理解のためにScalaでミスマッチを入力する


81

この構造がScalaで型の不一致エラーを引き起こすのはなぜですか?

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)

2
失敗した例でScalaが戻ると期待した結果は何ですか?
ダニエルC.ソブラル2011年

1
私がそれを書いていたとき、私はOption [List [(Int、Int)]]を取得すると思いました。
フェリペ鎌倉

回答:


117

内包表記は、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、理解のための結果はもちろんになります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


31

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

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

なぜこの制限があり、常に何らかのシーケンスが必要になるとは思わないのですか?戻ることが理にかなっている状況が発生する可能性があり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はOptionまたはListにすることができます。一般に、同じタイプである必要がありMます。したがって、OptionとListを組み合わせることはできません。可能性のある他のものの例については、この特性のサブクラスをM見てください。

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

つまり、式に返すタイプを検討し、そのタイプを最初のジェネレーターとして開始します。必要に応じて、そのタイプでラップします。


通常のfor構文でこのタイプのファンクター/モナド脱糖を行うのは悪い設計上の選択だと私は主張します。ファンクター/モナドマッピング用に別の名前のメソッド(など)を用意し、他の主流のプログラミング言語からの期待に一致する非常に単純な動作をするように構文をfmap予約forしてみませんか?
ely 2018

主流のシーケンシャルコンピューティング制御フローステートメントを非常に驚くべきものにしたり、パフォーマンスの複雑さを微妙に変えたりすることなく、個別のfmap / liftのようなものを好きなだけ一般的にすることができます。
ely 2018

4

それはおそらく、オプションが反復可能ではないことと関係があります。暗黙的Option.option2Iterableは、コンパイラが2番目がIterableであることを期待している場合を処理します。コンパイラの魔法は、ループ変数の種類によって異なると思います。


1

私はいつもこれが役に立ちました:

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)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.