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があいまいさについて文句を言わないのはなぜですか?
注:この質問は この他の質問 に触発され、より一般的な方法で問題を述べることを期待していた。回答で参照されているため、サンプルはそこからコピーされました。
Scalaの暗黙は、いわば「自動的に」渡すことができる値、または自動的に行われるあるタイプから別のタイプへの変換のいずれかを指します。
後者のタイプについて非常に簡単に言えば、クラスm
のオブジェクトo
でメソッドC
を呼び出し、そのクラスがメソッドm
をサポートしていない場合、ScalaはC
からdoesはm
をサポートします。簡単な例は、map
のメソッドString
です:
"abc".map(_.toInt)
String
はメソッドmap
をサポートしていませんが、StringOps
はサポートしています。また、String
からStringOps
への暗黙的な変換が使用可能です(Predef
のimplicit 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]
への暗黙的な変換が可能な限り、任意のオブジェクトを受け取ることができます。そのため、String
をgetIndex
に渡すことができ、それは機能します。
舞台裏では、コンパイラはseq.IndexOf(value)
をconv(seq).indexOf(value)
に変更します。
これは非常に便利なので、それらを記述するための構文糖衣があります。この構文糖を使用して、getIndex
は次のように定義できます。
def getIndex[T, CC <% Seq[T]](seq: CC, value: T) = seq.indexOf(value)
この構文糖はview bound、upper bound(CC <: Seq[Int]
)またはlowerに似ていますbound(T >: 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
に暗黙的に渡すことができます。
コンパイラがオブジェクトのクラスに存在しないメソッドを呼び出しているため、または暗黙的なパラメーターを必要とするメソッドを呼び出しているために、コンパイラーが暗黙的な必要性を認識すると、ニーズに合う暗黙的なものを検索します。
この検索は、どの暗黙的が表示され、どの暗黙的が表示されないかを定義する特定の規則に従います。コンパイラが暗黙を検索する場所を示す次の表は、Josh Suerethによる暗黙の presentation から抜粋したものです。Scalaの知識を向上させたい方には心からお勧めします。それ以来、フィードバックと更新で補完されています。
以下の番号1で使用可能な暗黙の数は、番号2のそれよりも優先されます。それ以外は、暗黙のパラメーターの型に一致する適格な引数がいくつかある場合、静的オーバーロード解決の規則を使用して最も具体的なものが選択されます(Scala仕様§6.26.3)。詳細な情報は、この回答の最後にあるリンク先の質問に記載されています。
それらのいくつかの例を挙げましょう:
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
への暗黙的な変換があるため、Iterable
でOption
メソッドを呼び出すか、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
の中にはそのようなものはなく、見るべき「ソース」タイプはありません。明らかに、A
のtype引数であるOrdering
内でそれを見つけています。
これは、CanBuildFrom
を必要とするさまざまなコレクションメソッドの動作方法でもあります。暗黙変数は、CanBuildFrom
の型パラメーターのコンパニオンオブジェクト内にあります。
注:Ordering
はtrait 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"
これは冗談だと確信していますが、この答えは最新ではないかもしれません。ですから、この質問を何が起こっているかの最終的な調停者であると受け取らないでください。もしあなたがそれが時代遅れになっていることに気付いたら、私がそれを修正できるように私に知らせてください。
編集
関連する質問:
探している場所だけでなく、暗黙的なパラメーター解決の優先順位を調べたかったので、ブログ投稿を書きました 輸入税なしで暗黙の再訪 (および 暗黙のパラメーター優先順位 =フィードバック後)。
リストは次のとおりです。
いずれかの段階で複数の暗黙的な静的オーバーロードルールが見つかった場合、それを解決します。