web-dev-qa-db-ja.com

Scala理解のためにタイプミスマッチ

この構造がScalaでType Mismatchエラーを引き起こすのはなぜですか?

for (first <- Some(1); second <- List(1,2,3)) yield (first,second)

<console>:6: error: type mismatch;
 found   : List[(Int, Int)]
 required: Option[?]
       for (first <- Some(1); second <- List(1,2,3)) yield (first,second)

リストでいくつかを切り替えると、うまくコンパイルされます:

for (first <- List(1,2,3); second <- Some(1)) yield (first,second)
res41: List[(Int, Int)] = List((1,1), (2,1), (3,1))

これもうまくいきます:

for (first <- Some(1); second <- Some(2)) yield (first,second)
76
Felipe Kamakura

内包表記は、mapまたはflatMapメソッドの呼び出しに変換されます。たとえば、これは:

_for(x <- List(1) ; y <- List(1,2,3)) yield (x,y)
_

それになります:

_List(1).flatMap(x => List(1,2,3).map(y => (x,y)))
_

したがって、最初のループ値(この場合、List(1))はflatMapメソッド呼び出しを受け取ります。 flatMap上のListは別のListを返すため、for内包表記の結果はもちろんListになります。 (これは私にとっては新しいことでした。理解のために、必ずしもSeqsでなくても、必ずしもストリームになるとは限りません。)

次に、flatMapOptionがどのように宣言されているかを見てみましょう。

_def flatMap [B] (f: (A) ⇒ Option[B]) : Option[B]
_

これを覚えておいてください。内包表記の誤り(Some(1)を持つもの)がどのように一連のマップ呼び出しに変換されるかを見てみましょう。

_Some(1).flatMap(x => List(1,2,3).map(y => (x, y)))
_

これで、flatMap呼び出しのパラメーターが、必要に応じてListではなくOptionを返すものであることが簡単にわかります。

問題を修正するために、次のことができます。

_for(x <- Some(1).toSeq ; y <- List(1,2,3)) yield (x, y)
_

それはうまくコンパイルされます。しばしば想定されるように、OptionSeqのサブタイプではないことに注意してください。

113
Madoc

覚えやすい簡単なヒントfor comprehensionsは、最初のジェネレーターのコレクションのタイプ、この場合はOption [Int]を返そうとします。したがって、Some(1)で開始する場合、Option [T]の結果を期待する必要があります。

List typeの結果が必要な場合は、リストジェネレーターから開始する必要があります。

なぜこの制限があり、常に何らかの種類のシーケンスが必要だと仮定しないのですか? Optionを返すことが理にかなっている場合があります。 Option[Int]を取得するために何かと組み合わせたいOption[List[Int]]があるかもしれません。たとえば、次の関数を使用します:(i:Int) => if (i > 0) List.range(0, i) else None;その後、これを記述して、物事が「意味をなさない」ときにNoneを取得できます。

val f = (i:Int) => if (i > 0) Some(List.range(0, i)) else None
for (i <- Some(5); j <- f(i)) yield j
// returns: Option[List[Int]] = Some(List(0, 1, 2, 3, 4))
for (i <- None; j <- f(i)) yield j
// returns: Option[List[Int]] = None
for (i <- Some(-3); j <- f(i)) yield j
// returns:  Option[List[Int]] = None

一般的な場合に内包表記がどのように展開されるかは、実際にはM[T]型のオブジェクトと(T) => M[U]型のオブジェクトを組み合わせてM[U]。あなたの例では、Mはオプションまたはリストです。一般に、同じタイプMでなければなりません。そのため、OptionとListを組み合わせることはできません。 Mになり得る他の例については、 この特性のサブクラス をご覧ください。

リストで始めたときに、なぜList[T](T) => Option[T]を組み合わせても機能したのですか?この場合、ライブラリは意味のあるより一般的なタイプを使用します。したがって、ListをTraversableと組み合わせることができ、OptionからTraversableへの暗黙的な変換があります。

一番下の行は次のとおりです。式に返すタイプを考え、最初のジェネレーターとしてそのタイプから始めます。必要に応じて、そのタイプでラップします。

31
huynhjl

OptionがIterableでないこととおそらく関係があります。暗黙的な Option.option2Iterable は、コンパイラが2番目がIterableであると想定している場合を処理します。コンパイラーのマジックは、ループ変数のタイプによって異なると思います。

4
sblundy

私はいつもこれが役立つと感じました:

scala> val foo: Option[Seq[Int]] = Some(Seq(1, 2, 3, 4, 5))
foo: Option[Seq[Int]] = Some(List(1, 2, 3, 4, 5))

scala> foo.flatten
<console>:13: error: Cannot prove that Seq[Int] <:< Option[B].
   foo.flatten
       ^

scala> val bar: Seq[Seq[Int]] = Seq(Seq(1, 2, 3, 4, 5))
bar: Seq[Seq[Int]] = List(List(1, 2, 3, 4, 5))

scala> bar.flatten
res1: Seq[Int] = List(1, 2, 3, 4, 5)

scala> foo.toSeq.flatten
res2: Seq[Int] = List(1, 2, 3, 4, 5)
0
user451151