web-dev-qa-db-ja.com

Scalaはどこで暗黙を探しますか?

Scalaの初心者へのimplicit質問は次のようです:コンパイラは暗黙の場所をどこで探しますか?言葉がなかったかのように、質問が完全に形成されることはないようだからです。 :-)たとえば、以下の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

最初の質問に対する答えを学ぶことにした人に続く別の質問は、明らかなあいまいさのある状況でコンパイラーがどの暗黙的使用を選択するかです(しかしそれはとにかくコンパイルします)?

たとえば、scala.Predefは、Stringから2つの変換を定義します。1つはWrappedStringに、もう1つはStringOpsに変換します。ただし、両方のクラスは多くのメソッドを共有しているので、mapを呼び出したときに、Scalaがあいまいさについて文句を言わないのはなぜですか?

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

387

暗黙のタイプ

Scalaの暗黙は、いわば「自動的に」渡すことができる値、または自動的に行われるあるタイプから別のタイプへの変換のいずれかを指します。

暗黙的な変換

後者のタイプについて非常に簡単に言えば、クラスmのオブジェクトoでメソッドCを呼び出し、そのクラスがメソッドmをサポートしていない場合、ScalaはCからdoesmをサポートします。簡単な例は、mapのメソッドStringです:

"abc".map(_.toInt)

Stringはメソッドmapをサポートしていませんが、StringOpsはサポートしています。また、StringからStringOpsへの暗黙的な変換が使用可能です(Predefimplicit def augmentStringを参照)。

暗黙的なパラメーター

暗黙のもう1つの種類は、暗黙のparameterです。これらは他のパラメーターと同様にメソッド呼び出しに渡されますが、コンパイラーはそれらを自動的に埋めようとします。それができない場合、文句を言います。 1つcanはこれらのパラメーターを明示的に渡します。これは、たとえば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]への暗黙的な変換が可能な限り、任意のオブジェクトを受け取ることができます。そのため、StringgetIndexに渡すことができ、それは機能します。

舞台裏では、コンパイラはseq.IndexOf(value)conv(seq).indexOf(value)に変更します。

これは非常に便利なので、それらを記述するための構文糖衣があります。この構文糖を使用して、getIndexは次のように定義できます。

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

この構文糖はview boundupper boundCC <: Seq[Int])またはlowerに似ていますboundT >: Null)。

コンテキスト境界

暗黙的なパラメーターのもう1つの一般的なパターンは、type class patternです。このパターンにより、それらを宣言しなかったクラスへの共通インターフェースの提供が可能になります。ブリッジパターンとして機能することもできます-懸念の分離を得る-とアダプターパターンとして。

あなたが言及した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)(_ + _)
}

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

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

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

Implicitsはどこから来たのですか?

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

この検索は、どの暗黙的が表示され、どの暗黙的が表示されないかを定義する特定の規則に従います。コンパイラが暗黙を検索する場所を示す次の表は、Josh Suerethによる暗黙の presentation から抜粋したものです。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)(_ + _)
}

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

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

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

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

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

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にはTraversableOnceが必要ですが、Optionはそうではありません。次に、コンパイラはOptionのオブジェクトコンパニオンの内部を調べ、IterableであるTraversableOnceへの変換を検出し、この式を修正します。

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

List(1, 2, 3).sorted

メソッドsortedは、暗黙のOrderingを取ります。この場合、クラスOrderingのコンパニオンであるオブジェクト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]Numeric[Long]を見つけたのは、NumericではなくIntegralの内部で見つかったためです。

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

引数タイプがAのメソッドがある場合、タイプ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]が必要です(B >: A)。 Orderingの中にはそのようなものはなく、見るべき「ソース」タイプはありません。明らかに、Atype引数であるOrdering内でそれを見つけています。

これは、CanBuildFromを必要とするさまざまなコレクションメソッドの動作方法でもあります。暗黙変数は、CanBuildFromの型パラメーターのコンパニオンオブジェクト内にあります。

Orderingtrait Ordering[T]として定義されます。ここで、Tは型パラメーターです。以前、Scalaは型パラメーターの内部を見ると言っていましたが、これはあまり意味がありません。上記の暗黙の検索はOrdering[A]です。ここで、Aは型パラメーターではなく、実際の型です。これは、Orderingに対するtype引数です。 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"

その他の寸法

これは冗談だと確信していますが、この答えは最新ではないかもしれません。ですから、この質問を何が起こっているかの最終的な調停者であると受け取らないでください。もしあなたがそれが時代遅れになっていることに気付いたら、私がそれを修正できるように私に知らせてください。

編集

関連する質問:

545

探している場所だけでなく、暗黙的なパラメーター解決の優先順位を調べたかったので、ブログ投稿を書きました 輸入税なしで暗黙の再訪 (および 暗黙のパラメーター優先順位 =フィードバック後)。

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

  • 1)ローカル宣言、インポート、外部スコープ、継承、プレフィックスなしでアクセス可能なパッケージオブジェクトを介して、現在の呼び出しスコープに可視の暗黙的。
  • 2)implicit scope。これには、検索する暗黙の型と何らかの関係があるすべての種類のコンパニオンオブジェクトとパッケージオブジェクト(つまり、型、型自体のコンパニオンオブジェクト、その型コンストラクタ(ある場合)、そのパラメータ(ある場合)、およびそのスーパータイプとスーパートレイト)。

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

23
Eugene Yokota