Predef のAPIドキュメントで、それらが汎用関数型のサブクラス(From)=> Toであることがわかりますが、それだけです。あの、何?どこかにドキュメントがあるかもしれませんが、検索エンジンは「<:<」のような「名前」をうまく処理できないので、見つけることができませんでした。
追加の質問:これらのファンキーなシンボル/クラスを使用する必要があるのはいつですか?
これらは、一般化型制約と呼ばれます。型パラメーター化されたクラスまたはトレイト内から、さらなる制約その型パラメーターの1つを使用できます。以下に例を示します。
case class Foo[A](a:A) { // 'A' can be substituted with any type
// getStringLength can only be used if this is a Foo[String]
def getStringLength(implicit evidence: A =:= String) = a.length
}
evidence
がA
である場合、暗黙の引数String
はコンパイラーによって提供されます。これはproofA
がString
であると考えることができます---引数自体は重要ではなく、存在することを知っているだけです。 [編集:まあ、技術的には、それはA
からString
への暗黙的な変換を表すため、実際に重要です。これにより、a.length
コンパイラはあなたに叫ぶ]
これで次のように使用できます。
scala> Foo("blah").getStringLength
res6: Int = 4
しかし、Foo
以外のものを含むString
で使用しようとした場合:
scala> Foo(123).getStringLength
<console>:9: error: could not find implicit value for parameter evidence: =:=[Int,String]
このエラーは、「Int == Stringという証拠を見つけることができませんでした」と読むことができます。 getStringLength
は、一般的にA
が必要とするものよりもFoo
のタイプにさらなる制限を課しています。つまり、Foo[String]
でのみgetStringLength
を呼び出すことができます。この制約はコンパイル時に適用されます。
<:<
と<%<
は同様に機能しますが、わずかな違いがあります。
A =:= B
は、Aが正確にBでなければならないことを意味しますA <:< B
は、AがBのサブタイプでなければならないことを意味します(simple型制約<:
に類似)A <%< B
は、Aがviewableでなければならないことを意味します。おそらく暗黙的な変換(単純型制約<%
に類似)を介してこのスニペット by @retronymは、この種のことをどのように達成し、一般化された型制約がどのように簡単になったのかをよく説明しています。
[〜#〜]補遺[〜#〜]
あなたのフォローアップの質問に答えるために、確かに私が与えた例はかなり不自然であり、明らかに有用ではありません。しかし、整数のリストを追加するList.sumInts
メソッドのようなものを定義するためにそれを使用することを想像してください。古いList
ではなく、List[Int]
でこのメソッドを呼び出すことはできません。ただし、List
型のコンストラクターはそれほど制限できません。あなたはまだ文字列、foo、bar、およびその他のリストを持ちたいと思っています。したがって、一般的な型制約をsumInts
に配置することにより、そのメソッドだけにList[Int]
でのみ使用できる追加の制約があることを確認できます。基本的に、特定の種類のリスト用の特別なケースのコードを書いています。
完全な答えではありません(他の人はすでにこれに答えています)。次のことに注意したいだけです。これは、構文をよりよく理解するのに役立つかもしれません。
def getStringLength(implicit evidence: A =:= String)
scalaの代替 型演算子の挿入構文 を使用します。
したがって、A =:= String
は=:=[A, String]
と同じです(および=:=
は、見た目が美しい名前のクラスまたはトレイトです)。この構文は「通常の」クラスでも機能することに注意してください。たとえば、次のように書くことができます。
val a: Tuple2[Int, String] = (1, "one")
このような:
val a: Int Tuple2 String = (1, "one")
これは、メソッド呼び出しの2つの構文、.
および()
を使用した「通常」、および演算子構文に似ています。
他の回答を読んで、これらの構造が何であるかを理解してください。 whenを使用する必要があります。特定のタイプのメソッドのみを制約する必要がある場合に使用します。
以下に例を示します。次のように、同種のペアを定義するとします。
_class Pair[T](val first: T, val second: T)
_
次のように、メソッドsmaller
を追加します。
_def smaller = if (first < second) first else second
_
T
が順序付けられている場合にのみ機能します。クラス全体を制限できます:
_class Pair[T <: Ordered[T]](val first: T, val second: T)
_
しかし、それは残念なようです。T
が順序付けされていない場合、クラスに使用できる可能性があります。型制約を使用すると、smaller
メソッドを引き続き定義できます。
_def smaller(implicit ev: T <:< Ordered[T]) = if (first < second) first else second
_
たとえば、_Pair[File]
_をインスタンス化しても構いません。呼び出さない限りsmaller
をインスタンス化します。
Option
の場合、実装者は_Option[Int]
_には意味がありませんが、orNull
メソッドが必要でした。型制約を使用することで、すべてがうまくいきます。 _Option[String]
_でorNull
を使用できます。また、orNull
を呼び出さない限り、_Option[Int]
_を形成して使用できます。 Some(42).orNull
を試すと、魅力的なメッセージが表示されます
_ error: Cannot prove that Null <:< Int
_
それらがどこで使用されているかによります。ほとんどの場合、暗黙的なパラメーターの型を宣言するときに使用すると、クラスになります。まれに、オブジェクトになることもあります。最後に、それらはManifest
オブジェクトの演算子になることができます。それらは最初の2つのケースでscala.Predef
内で定義されていますが、特に文書化されていません。
これらは、<:
と<%
のように、クラスを使用できない場合にクラス間の関係をテストする方法を提供することを目的としています。
「いつ使うべきか」という質問については、そうすべきだとわかっているのでなければ、答えてはいけません。 :-) [〜#〜] edit [〜#〜]:OK、OK、ここにライブラリの例をいくつか示します。 Either
には、次のものがあります。
/**
* Joins an <code>Either</code> through <code>Right</code>.
*/
def joinRight[A1 >: A, B1 >: B, C](implicit ev: B1 <:< Either[A1, C]): Either[A1, C] = this match {
case Left(a) => Left(a)
case Right(b) => b
}
/**
* Joins an <code>Either</code> through <code>Left</code>.
*/
def joinLeft[A1 >: A, B1 >: B, C](implicit ev: A1 <:< Either[C, B1]): Either[C, B1] = this match {
case Left(a) => a
case Right(b) => Right(b)
}
Option
には、次のものがあります。
def orNull[A1 >: A](implicit ev: Null <:< A1): A1 = this getOrElse null
コレクションには他にもいくつかの例があります。
In Scala 2.13それらはPredef
から移動されました: Move <:<、=:=、DummyImplicits out from Predef#7350
feature の型制約は、他の回答では明示的にされていない可能性があります。
... constrainany抽象型
T
that'sin scopeメソッドの引数リスト内(メソッド自体の型パラメーターだけでなく)
"メソッド自体の型パラメーターだけでなく"アスペクトを示す例を次に示します。私たちが持っていると言う
case class Foo[A, B](f: A => B) {
def bar[C <: A](x: C)(implicit e: B <:< String): B = f(x)
}
Foo[Int, String](x => x.toString).bar(1) // OK.
Foo[Int, Double](x => x.toDouble).bar(1) // error: Cannot prove that Double <:< String.
B
の型パラメーター句[C <: A]
にないにもかかわらず、どのように型パラメーターbar
を制約できるかに注意してください。代わりにB
の型パラメーター節でbar
を制約しようとした場合
def bar[B <: String]
Foo[A, B]
の囲みスコープからB
型パラメーターをシャドウイングします。ライブラリからのこの実際の例は、 toMap
:
trait IterableOnceOps[+A, +CC[_], +C] extends Any {
...
def toMap[K, V](implicit ev: A <:< (K, V)): immutable.Map[K, V] =
...
}