Scala は暗黙の要素をどこで探すのでしょうか? 質問する

Scala は暗黙の要素をどこで探すのでしょうか? 質問する

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 つの疑問は、明らかに曖昧であるが、とにかくコンパイルされる特定の状況で、コンパイラーはどの暗黙的表現を使用するかをどのように選択するかということです。

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

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

ベストアンサー1

暗黙の種類

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

暗黙的な変換

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

"abc".map(_.toInt)

Stringはメソッド をサポートしていませmapんが、はサポートしており、からStringOpsへの暗黙的な変換が利用可能です (を参照)。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を に渡すことができ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)(_ + _)
}

これには、コンテキスト バウンドと呼ばれる構文糖衣も存在しますが、暗黙のものを参照する必要があるため、あまり役に立ちません。そのメソッドを直接変換すると、次のようになります。

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)(_ + _)
}

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

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

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

Implicits はどこから来るのでしょうか?

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

この検索は、どの暗黙的要素が可視でどれが不可視かを定義する特定のルールに従います。コンパイラが暗黙的要素を検索する場所を示す次の表は、優れたプレゼンテーション(タイムスタンプ 20:20) Josh Suereth による implicit に関する記事です。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への暗黙的な変換があるためIterableIterableのメソッドを呼び出したりOptionOptionを期待するものに渡したりすることができます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、この式は正しいものになります。

2 番目は、期待されるタイプのコンパニオン オブジェクトです。

List(1, 2, 3).sorted

メソッドはsorted暗黙の を受け取ります。この場合、クラス のコンパニオンであるOrderingオブジェクト の内部を調べ、そこに暗黙の を見つけます。OrderingOrderingOrdering[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]内部にあるためです。NumericIntegral

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

引数 type を持つメソッドがある場合A、 type の暗黙的なスコープ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ある 内でそれを見つけています。AOrdering

これは、 が期待するさまざまなコレクション メソッドの動作方法でもあります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"

その他の次元

これは冗談だったと思いますが、この回答は最新ではない可能性があります。したがって、この質問を、何が起こっているかの最終的な判断基準と見なさないでください。また、この回答が古くなっていることに気付いた場合は、修正できるようにお知らせください。

編集

関連する興味深い質問:

おすすめ記事