422

Scala の初心者への暗黙の質問は、次のように思われます: コンパイラはどこで暗黙を探しますか? まるで言葉がないかのように、質問が完全に形成されないように見えるため、暗黙的という意味です。:-) たとえば、integral以下の値はどこから来たのですか?

scala> import scala.math._
import scala.math._

scala> def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}
foo: [T](t: T)(implicit integral: scala.math.Integral[T])Unit

scala> foo(0)
scala.math.Numeric$IntIsIntegral$@3dbea611

scala> foo(0L)
scala.math.Numeric$LongIsIntegral$@48c610af

最初の質問に対する答えを学ぶことを決定した人に続くもう 1 つの質問は、明らかにあいまいな特定の状況で、コンパイラーがどの暗黙のうちに使用するものをどのように選択するのかということです (ただし、とにかくコンパイルします)。

たとえば、から への変換と から への変換の 2つをscala.Predef定義します。しかし、どちらのクラスも多くのメソッドを共有しています。StringWrappedStringStringOpsmap

注:この質問は、より一般的な方法で問題を説明することを期待して、この他の質問に触発されました。例は、回答で参照されているため、そこからコピーされました。

4

2 に答える 2

589

暗黙のタイプ

Scala の Implicits は、いわば「自動的に」渡される値、または自動的に行われるある型から別の型への変換のいずれかを指します。

暗黙の変換

後者の型について簡単に説明すると、 classmのオブジェクトでメソッドを呼び出し、そのクラスが method をサポートしてない場合、Scala は から をサポートするものへの暗黙的な変換を探します。簡単な例はonのメソッドです:oCmCmmapString

"abc".map(_.toInt)

Stringは methodmapをサポートしていませんがサポートしており、からavailableStringOpsへの暗黙的な変換があります ( onを参照)。StringStringOpsimplicit def augmentStringPredef

暗黙的なパラメーター

もう 1 つの種類の暗黙的なものは、暗黙的なパラメーターです。これらは他のパラメーターと同様にメソッド呼び出しに渡されますが、コンパイラーはそれらを自動的に入力しようとします。できない場合は、文句を言います。たとえば、これらのパラメータを明示的に渡すことができbreakOutます。これは、たとえば を使用する方法です (breakOut挑戦したい気分になっている日に、 に関する質問を参照してください)。

fooこの場合、メソッド宣言などの暗黙の必要性を宣言する必要があります。

def foo[T](t: T)(implicit integral: Integral[T]) {println(integral)}

境界を表示

暗黙が暗黙の変換と暗黙のパラメーターの両方である状況が 1 つあります。例えば:

def getIndex[T, CC](seq: CC, value: T)(implicit conv: CC => Seq[T]) = seq.indexOf(value)

getIndex("abc", 'a')

メソッドgetIndexは、そのクラスから への暗黙的な変換が利用できる限り、任意のオブジェクトを受け取ることができますSeq[T]Stringそのため、 toを渡すことができ、getIndex動作します。

舞台裏で、コンパイラseq.IndexOf(value)conv(seq).indexOf(value).

これは非常に便利なので、それらを書くための構文糖衣があります。このシンタックス シュガーを使用すると、getIndex次のように定義できます。

def getIndex[T, CC <% Seq[T]](seq: CC, value: T) = seq.indexOf(value)

このシンタックス シュガーは、上限( ) または下限( )に似たビュー バウンドとして記述されます。CC <: Seq[Int]T >: Null

コンテキスト境界

暗黙的なパラメーターのもう 1 つの一般的なパターンは、型クラス パターンです。このパターンにより、宣言されていないクラスへの共通インターフェースの提供が可能になります。これは、ブリッジ パターン (懸念事項の分離を得る) としても、アダプター パターンとしても機能します。

あなたが言及したIntegralクラスは、型クラス パターンの典型的な例です。Scala の標準ライブラリの別の例はOrdering. このパターンを多用する Scalaz というライブラリがあります。

これはその使用例です:

def sum[T](list: List[T])(implicit integral: Integral[T]): T = {
    import integral._   // get the implicits in question into scope
    list.foldLeft(integral.zero)(_ + _)
}

また、 context boundと呼ばれるシンタックス シュガーもありますが、これは暗黙的なものを参照する必要があるため、あまり役に立ちません。そのメソッドを直接変換すると、次のようになります。

def sum[T : Integral](list: List[T]): T = {
    val integral = implicitly[Integral[T]]
    import integral._   // get the implicits in question into scope
    list.foldLeft(integral.zero)(_ + _)
}

コンテキスト境界は、それらを使用する他のメソッドに渡す必要がある場合に便利です。たとえば、メソッドsortedon にSeqは暗黙の が必要Orderingです。メソッドを作成するにはreverseSort、次のように記述できます。

def reverseSort[T : Ordering](seq: Seq[T]) = seq.sorted.reverse

Ordering[T]は に暗黙的に渡されたためreverseSort、 に暗黙的に渡すことができsortedます。

Implicit はどこから来るのか?

オブジェクトのクラスに存在しないメソッドを呼び出しているか、暗黙のパラメーターを必要とするメソッドを呼び出しているために、コンパイラーが暗黙の必要性を認識すると、必要に適合する暗黙のパラメーターを検索します。 .

この検索は、どの Implicit が表示され、どれが表示されないかを定義する特定の規則に従います。コンパイラが暗黙を検索する場所を示す次の表は、Josh Suereth による暗黙に関する優れたプレゼンテーション(タイムスタンプ 20:20) から引用したものです。Scala の知識を向上させたいすべての人に心からお勧めします。それ以来、フィードバックと更新によって補完されてきました。

以下の番号 1 で使用可能な暗黙は、番号 2 の下のものよりも優先されます。それ以外に、暗黙のパラメーターの型に一致する適切な引数が複数ある場合は、静的オーバーロード解決の規則を使用して、最も具体的な引数が選択されます (Scala を参照)。仕様 §6.26.3)。より詳細な情報は、この回答の最後にあるリンク先の質問に記載されています。

  1. 現在のスコープの最初のルック
    • 現在のスコープで定義されている暗黙
    • 明示的なインポート
    • ワイルドカードのインポート
    • 他のファイルの同じスコープ
  2. 関連する型を見てみましょう
    • タイプのコンパニオン オブジェクト
    • 引数の型の暗黙のスコープ(2.9.1)
    • 型引数の暗黙のスコープ(2.8.0)
    • ネストされた型の外部オブジェクト
    • その他の寸法

それらの例をいくつか挙げましょう。

現在のスコープで定義された暗黙

implicit val n: Int = 5
def add(x: Int)(implicit y: Int) = x + y
add(5) // takes n from the current scope

明示的なインポート

import scala.collection.JavaConversions.mapAsScalaMap
def env = System.getenv() // Java map
val term = env("TERM")    // implicit conversion from Java Map to Scala Map

ワイルドカードのインポート

def sum[T : Integral](list: List[T]): T = {
    val integral = implicitly[Integral[T]]
    import integral._   // get the implicits in question into scope
    list.foldLeft(integral.zero)(_ + _)
}

他のファイルの同じスコープ

編集:これには別の優先順位がないようです。優先順位の違いを示す例があれば、コメントしてください。それ以外の場合は、これに依存しないでください。

これは最初の例に似ていますが、暗黙的な定義がその使用法とは別のファイルにあると仮定しています。また、パッケージ オブジェクトを使用して暗黙的に使用する方法も参照してください。

タイプのコンパニオン オブジェクト

ここで注目すべき 2 つのオブジェクト コンパニオンがあります。まず、「ソース」タイプのオブジェクト コンパニオンを調べます。たとえば、オブジェクト内Optionでは への暗黙的な変換があるため、 でメソッドをIterable呼び出したり、を期待するものに渡したりできます。例えば:IterableOptionOptionIterable

for {
    x <- List(1, 2, 3)
    y <- Some('x')
} yield (x, y)

その式は、コンパイラによって次のように変換されます。

List(1, 2, 3).flatMap(x => Some('x').map(y => (x, y)))

ただし、そうではない をList.flatMap期待します。次に、コンパイラはのオブジェクト コンパニオンの内部を調べ、 への変換を見つけます。これは であり、この式を正しくします。TraversableOnceOptionOptionIterableTraversableOnce

次に、予想されるタイプのコンパニオン オブジェクト:

List(1, 2, 3).sorted

このメソッドsortedは暗黙の を受け取りますOrdering。この場合、Orderingクラスのコンパニオンである object の内部を調べ、そこにOrdering暗黙的な を見つけOrdering[Int]ます。

スーパー クラスのコンパニオン オブジェクトも調査されることに注意してください。例えば:

class A(val n: Int)
object A { 
    implicit def str(a: A) = "A: %d" format a.n
}
class B(val x: Int, y: Int) extends A(y)
val b = new B(5, 2)
val s: String = b  // s == "A: 2"

これは、Scalaがあなたの質問で暗黙的Numeric[Int]andを見つけた方法です。Numeric[Long]ちなみに、それらはNumericではなく内にあるためIntegralです。

引数の型の暗黙のスコープ

引数 type を持つメソッドがある場合、 typeAの暗黙のスコープAも考慮されます。「暗黙のスコープ」とは、これらすべてのルールが再帰的に適用されることを意味します。たとえば、上記のルールに従って、のコンパニオン オブジェクトはA暗黙的に検索されます。

これはA、そのパラメーターの変換のために の暗黙のスコープが検索されることを意味するのではなく、式全体が検索されることを意味することに注意してください。例えば:

class A(val n: Int) {
  def +(other: A) = new A(n + other.n)
}
object A {
  implicit def fromInt(n: Int) = new A(n)
}

// This becomes possible:
1 + new A(1)
// because it is converted into this:
A.fromInt(1) + new A(1)

これは Scala 2.9.1 以降で利用可能です。

型引数の暗黙のスコープ

これは、型クラス パターンを実際に機能させるために必要です。たとえば、 を考えてみましょうOrdering: コンパニオン オブジェクトにはいくつかの暗黙のオブジェクトが付属していますが、それに何かを追加することはできません。Orderingでは、自動的に検出される独自のクラスを作成するにはどうすればよいでしょうか。

実装から始めましょう。

class A(val n: Int)
object A {
    implicit val ord = new Ordering[A] {
        def compare(x: A, y: A) = implicitly[Ordering[Int]].compare(x.n, y.n)
    }
}

だから、あなたが電話したときに何が起こるかを考えてください

List(new A(5), new A(2)).sorted

見てきたように、このメソッドは をsorted期待していますOrdering[A](実際にはOrdering[B]、 whereを期待していB >: Aます)。内部にはそのようなものOrderingはなく、調べる「ソース」タイプもありません。明らかに、の型引数Aある 内でそれを見つけています。Ordering

CanBuildFromこれは、動作を期待するさまざまなコレクション メソッドの方法でもありますCanBuildFrom

:Orderingは として定義されます。trait Ordering[T]ここTで、 は型パラメーターです。前に、Scala は型パラメーターの内部を調べると言いましたが、これはあまり意味がありません。上記の暗黙の検索は ですOrdering[A]。ここでA、 は型パラメーターではなく実際の型です。これは の型引数ですOrdering。Scala 仕様のセクション 7.2 を参照してください。

これは Scala 2.8.0 以降で利用可能です。

ネストされた型の外部オブジェクト

私は実際にこれの例を見たことがありません。どなたかシェアしていただけるとありがたいです。原理は簡単です:

class A(val n: Int) {
  class B(val m: Int) { require(m < n) }
}
object A {
  implicit def bToString(b: A#B) = "B: %d" format b.m
}
val a = new A(5)
val b = new a.B(3)
val s: String = b  // s == "B: 3"

その他の寸法

これは冗談だったと確信していますが、この回答は最新ではない可能性があります。したがって、この質問を、何が起こっているかの最終決定者であるとは考えないでください。もしそれが時代遅れになっていることに気付いた場合は、私に知らせてください。私が修正できるようにします.

編集

関連する質問:

于 2011-04-08T16:31:53.147 に答える
24

暗黙のパラメーター解決の優先順位を、探す場所だけでなく、見つけたかったので、輸入税のない暗黙のパラメーターを再検討するブログ投稿を書きました(そして、いくつかのフィードバックの後、暗黙のパラメーターの優先順位をもう一度)。

リストは次のとおりです。

  • 1) ローカル宣言、インポート、外部スコープ、継承、接頭辞なしでアクセスできるパッケージ オブジェクトを介して、現在の呼び出しスコープに表示される暗黙。
  • 2)暗黙のスコープ。これには、検索する暗黙の型と何らかの関係を持つすべての種類のコンパニオン オブジェクトとパッケージ オブジェクトが含まれます (つまり、型のパッケージ オブジェクト、型自体のコンパニオン オブジェクト、存在する場合はその型コンストラクターのコンパニオン オブジェクト)そのパラメータがあれば、そのスーパータイプとスーパートレイトも)。

いずれかの段階で複数の暗黙的なオーバーロードが見つかった場合は、それを解決するために静的なオーバーロード ルールが使用されます。

于 2012-01-01T18:05:55.550 に答える