web-dev-qa-db-ja.com

<= <、<%<、および=:=は、Scala 2.8で意味し、どこに文書化されていますか?

Predef のAPIドキュメントで、それらが汎用関数型のサブクラス(From)=> Toであることがわかりますが、それだけです。あの、何?どこかにドキュメントがあるかもしれませんが、検索エンジンは「<:<」のような「名前」をうまく処理できないので、見つけることができませんでした。

追加の質問:これらのファンキーなシンボル/クラスを使用する必要があるのはいつですか?

192
Jeff

これらは、一般化型制約と呼ばれます。型パラメーター化されたクラスまたはトレイト内から、さらなる制約その型パラメーターの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
}

evidenceAである場合、暗黙の引数Stringはコンパイラーによって提供されます。これはproofAStringであると考えることができます---引数自体は重要ではなく、存在することを知っているだけです。 [編集:まあ、技術的には、それは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]でのみ使用できる追加の制約があることを確認できます。基本的に、特定の種類のリスト用の特別なケースのコードを書いています。

203
Tom Crockett

完全な答えではありません(他の人はすでにこれに答えています)。次のことに注意したいだけです。これは、構文をよりよく理解するのに役立つかもしれません。

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つの構文、.および()を使用した「通常」、および演算子構文に似ています。

53
Jesper

他の回答を読んで、これらの構造が何であるかを理解してください。 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
_
38
cayhorstmann

それらがどこで使用されているかによります。ほとんどの場合、暗黙的なパラメーターの型を宣言するときに使用すると、クラスになります。まれに、オブジェクトになることもあります。最後に、それらは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

コレクションには他にもいくつかの例があります。

17

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] =
  ...
}
0
Mario Galic