この構造が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)
内包表記は、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
になります。 (これは私にとっては新しいことでした。理解のために、必ずしもSeq
sでなくても、必ずしもストリームになるとは限りません。)
次に、flatMap
でOption
がどのように宣言されているかを見てみましょう。
_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)
_
それはうまくコンパイルされます。しばしば想定されるように、Option
はSeq
のサブタイプではないことに注意してください。
覚えやすい簡単なヒント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への暗黙的な変換があります。
一番下の行は次のとおりです。式に返すタイプを考え、最初のジェネレーターとしてそのタイプから始めます。必要に応じて、そのタイプでラップします。
OptionがIterableでないこととおそらく関係があります。暗黙的な Option.option2Iterable
は、コンパイラが2番目がIterableであると想定している場合を処理します。コンパイラーのマジックは、ループ変数のタイプによって異なると思います。
私はいつもこれが役立つと感じました:
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)