Dlaczego ta konstrukcja powoduje błąd niezgodności typu w Scali?
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)
Jeśli przełączę Some with the List, kompiluje się dobrze:
for (first <- List(1,2,3); second <- Some(1)) yield (first,second)
res41: List[(Int, Int)] = List((1,1), (2,1), (3,1))
To również działa dobrze:
for (first <- Some(1); second <- Some(2)) yield (first,second)
scala
for-loop
type-mismatch
for-comprehension
scala-option
Felipe Kamakura
źródło
źródło
Odpowiedzi:
Dla zrozumienia są konwertowane na wywołania metody
map
lubflatMap
. Na przykład ten:for(x <- List(1) ; y <- List(1,2,3)) yield (x,y)
staje się:
List(1).flatMap(x => List(1,2,3).map(y => (x,y)))
Dlatego pierwsza wartość pętli (w tym przypadku
List(1)
) otrzymaflatMap
wywołanie metody. PonieważflatMap
aList
zwraca innyList
, wynikiem zrozumienia będzie oczywiście aList
. (To było dla mnie nowe: rozumienie nie zawsze prowadzi do strumieni, a nawet niekoniecznie wSeq
s.)Teraz spójrz, jak
flatMap
jest deklarowane wOption
:def flatMap [B] (f: (A) ⇒ Option[B]) : Option[B]
Pamiętaj o tym. Zobaczmy, jak błędne do zrozumienia (to z
Some(1)
) jest konwertowane na sekwencję wywołań mapy:Some(1).flatMap(x => List(1,2,3).map(y => (x, y)))
Teraz łatwo zauważyć, że parametr
flatMap
wywołania jest czymś, co zwraca aList
, ale nieOption
, zgodnie z wymaganiami.Aby to naprawić, możesz wykonać następujące czynności:
for(x <- Some(1).toSeq ; y <- List(1,2,3)) yield (x, y)
To się dobrze komponuje. Warto zauważyć, że
Option
nie jest to podtypSeq
, jak się często zakłada.źródło
Łatwa do zapamiętania wskazówka, ponieważ wyrażenia spróbują zwrócić typ zbioru pierwszego generatora, w tym przypadku Option [Int]. Tak więc, jeśli zaczniesz od Some (1) , powinieneś spodziewać się wyniku opcji [T].
Jeśli chcesz otrzymać wynik typu Lista , powinieneś zacząć od generatora list.
Dlaczego masz to ograniczenie i nie zakładać, że zawsze będziesz chciał jakiejś sekwencji? Możesz mieć sytuację, w której powrót ma sens
Option
. Może masz cośOption[Int]
, co chcesz połączyć z czymś, aby uzyskaćOption[List[Int]]
, powiedzmy z następującą funkcją(i:Int) => if (i > 0) List.range(0, i) else None
:; możesz następnie napisać to i uzyskać Brak, gdy coś nie ma "sensu":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
Jak dla listowe są rozszerzane w ogólnym przypadku są w rzeczywistości dość ogólny mechanizm połączyć obiekt typu
M[T]
z funkcją(T) => M[U]
uzyskać obiektu typuM[U]
. W twoim przykładzie M może oznaczać Option lub List. Generalnie musi być tego samego typuM
. Więc nie możesz łączyć Option z Listą. Aby zobaczyć przykłady innych rzeczy, które mogą byćM
, spójrz na podklasy tej cechy .Dlaczego jednak połączyłeś się
List[T]
z(T) => Option[T]
pracą, kiedy zaczynałeś z Listą? W tym przypadku biblioteka używa bardziej ogólnego typu tam, gdzie ma to sens. Możesz więc połączyć List z Traversable i istnieje niejawna konwersja z Option na Traversable.Najważniejsze jest to: zastanów się, jaki typ ma zwrócić wyrażenie i zacznij od tego typu jako pierwszego generatora. W razie potrzeby zawiń go w ten typ.
źródło
for
składnia wykonywała tego typu funktor / monadyczne desugowanie. Dlaczego nie mieć inaczej nazwanych metod mapowania funktorów / monad, takich jakfmap
itp., I zarezerwowaćfor
składnię, aby uzyskać niezwykle proste zachowanie, które odpowiada oczekiwaniom pochodzącym z praktycznie każdego innego głównego nurtu języka programowania?Prawdopodobnie ma to coś wspólnego z tym, że opcja nie jest iterowalna. Niejawne
Option.option2Iterable
obsłuży przypadek, w którym kompilator oczekuje, że sekunda będzie iterowalna. Spodziewam się, że magia kompilatora różni się w zależności od typu zmiennej pętli.źródło
Zawsze uważałem to za pomocne:
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)
źródło