1. ホーム
  2. スカラ

[解決済み】Scalaはどこでimplicitを探すのか?

2022-05-02 13:19:20

質問

について 暗黙の 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 . しかし,どちらのクラスも多くのメソッドを共有しています. map ?

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

どのように解決するのですか?

インプリシットの種類

Scalaにおけるインプリシットは、いわば自動的に渡される値、あるいは自動的に行われるある型から別の型への変換のことを指します。

暗黙の変換

後者のタイプについてごく簡単に説明すると、あるメソッドを呼び出すと、そのメソッドは m オブジェクトの o あるクラスの C をサポートせず、そのクラスがメソッド m からの暗黙の変換を探します。 C というものに する サポート m . 簡単な例としては、メソッド map について String :

"abc".map(_.toInt)

String は、メソッドをサポートしていません。 map しかし StringOps という暗黙の変換があります。 String から StringOps が利用可能です(参照 implicit def augmentString について Predef ).

暗黙のパラメータ

もう一つの暗黙は、暗黙の パラメータ . これらは他のパラメータと同様にメソッド呼び出しに渡されますが、コンパイラはそれらを自動的に埋めようとします。もし埋められない場合は、文句を言われます。ひとつは できる これらのパラメータは明示的に渡します。 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 ).

コンテキスト・バウンド

暗黙のパラメータでもう一つよくあるパターンは 型クラスパターン . このパターンは、共通のインターフェイスを宣言していないクラスに対して、共通のインターフェイスを提供することを可能にします。ブリッジ・パターン(関心事の分離)としても、アダプター・パターンとしても機能します。

この 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 .

インプリシットはどこから来たのか?

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

この検索は、どのインプリシットが表示され、どのインプリシットが表示されないかを定義する一定の規則に従います。次の表は、コンパイラがインプリシットを検索する場所を示すものです。 プレゼンテーション Josh Suerethによるimplicitsについての記事で、Scalaの知識を高めたい人には心からお勧めします。その後、フィードバックやアップデートによって補完されています。

以下の1番で利用できるインプリシットは、2番のものよりも優先されます。それ以外では、暗黙のパラメータの型にマッチする引数が複数ある場合、静的オーバーロードの解決ルール(Scala Specification §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つのオブジェクトコンパニオンです。まず、"source"タイプのオブジェクトコンパニオンが調べられます。例えば、オブジェクトの内部 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.flatMapTraversableOnce は、どの 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

見たように、メソッド sortedOrdering[A] (実際には Ordering[B] で、ここで B >: A ). の中にはそんなものはない。 Ordering そして、探すべき "source" 型もありません。明らかに、それは 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"

その他の寸法

これは冗談だと思いますが、この回答は最新でない可能性があります。ですから、この質問が最終的な判断材料になるとは思わないでください。もし、古くなっていることに気づいたら、修正しますので、お知らせください。

EDIT

気になる関連質問