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


398

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

最初の質問への回答を学ぶことにした人に続く別の質問は、明白な曖昧さのある特定の状況(しかし、とにかくコンパイル)で、コンパイラがどの暗黙の使用を選択するかです。

例えば、scala.Predefから2つの変換を定義StringするいずれかWrappedStringと相互にStringOps。ただし、どちらのクラスも多くのメソッドを共有しているので、たとえば、Scalaが呼び出し時にあいまいさについて不平を言わないのはなぜmapですか?

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

回答:


554

暗黙のタイプ

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

暗黙的な変換

1のメソッドを呼び出す場合、後者のタイプについては非常に簡単に言えば、mオブジェクト上oのクラスのC、そのクラスはメソッドをサポートしていないm場合、Scalaはからの暗黙的な変換を探しますC何かにない支援をm。簡単な例では、方法になりますmapString

"abc".map(_.toInt)

Stringメソッドをサポートしていませんmapが、StringOpsない、との暗黙的な変換がありますStringStringOps(参照可能なのimplicit def augmentStringPredef)。

暗黙的なパラメーター

他の種類の暗黙的なものは、暗黙的なパラメータです。これらは他のパラメーターと同様にメソッド呼び出しに渡されますが、コンパイラーはそれらを自動的に埋めようとします。それができない場合は、文句を言うでしょう。一つは、することができますどのようにいずれかを使用している、明示的にこれらのパラメータを渡す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]。そのため、Stringto を渡すことができ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の標準ライブラリのもう1つの例は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)(_ + _)
}

コンテキストバウンドと呼ばれるそのためのシンタックスシュガーもあり、暗黙的に参照する必要があるため、あまり有用ではありません。そのメソッドを直接変換すると、次のようになります。

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

インプリシットはどこから来るのですか?

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

この検索は、どのインプリシットが表示され、どのインプリシットが表示されないかを定義する特定のルールに従います。コンパイラーがインプリシットを検索する場所を示す次の表は、Josh Suerethによるインプリシットに関する優れたプレゼンテーションから引用したものです。それ以来、フィードバックとアップデートで補完されています。

以下の番号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、のIterableメソッドを呼び出すか、を期待するものにOption渡すことができます。例えば:OptionIterable

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、クラスのコンパニオンを調べOrderingOrdering[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です。

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

引数typeを持つメソッドがある場合、type 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はなく、調べる「ソース」タイプもありません。明らかに、それは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"

その他の寸法

これは冗談だったと思いますが、この答えは最新のものではないかもしれません。ですから、この質問を何が起こっているかの最終的な判断者だと考えないでください。古くなっていることに気付いた場合は、私が修正できるようにお知らせください。

編集

関心のある関連質問:


60
あなたが本であなたの答えを使い始める時が来ました、今ではそれをすべてまとめるだけの問題です。
pedrofurla

3
@pedrofurla私はポルトガル語で本を書くと考えられてきました。誰かが私にテクニカルパブリッシャーとの連絡先を見つけることができる場合...
ダニエルC.ソブラル

2
タイプのパーツのコンパニオンのパッケージオブジェクトも検索されます。lampsvn.epfl.ch/trac/scala/ticket/4427
レトロニム

1
この場合、それは暗黙のスコープの一部です。呼び出しサイトはそのパッケージ内にある必要はありません。それは私にとって驚くべきことでした。
レトロニム

2
そうです、stackoverflow.com / questions / 8623055は具体的にそれをカバーしていますが、私はあなたが「次のリストは優先順位で提示されることを意図しています...報告してください」と書いたことに気づきました。基本的に、すべてのウェイトが等しいため(少なくとも2.10では)、内部リストは順序付けされていないはずです。
Eugene Yokota

23

暗黙的なパラメーター解決の優先順位を探す場所だけでなく、その優先順位も知りたいと思ったので、輸入税なし暗黙的にパラメーターを再検討するブログ投稿(およびフィードバックの後で暗黙的なパラメーターの優先順位を再度)を書きました。

ここにリストがあります:

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

いずれかの段階で複数の暗黙的な静的オーバーロードルールを使用してそれを解決した場合。


3
これは、パッケージ、オブジェクト、トレイト、クラスを定義するだけのコードを記述し、スコープを参照するときにそれらの文字を使用すると改善される可能性があります。メソッドの宣言をまったく行う必要はありません。名前とだれが誰をどのスコープで拡張するかだけです。
ダニエルC.ソブラル2012年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.