Введіть невідповідність на шкалі для розуміння


81

Чому ця конструкція спричиняє помилку Type Mismatch у 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 у невдалому прикладі?
Daniel C. Sobral

1
Коли я писав його, я думав, що отримаю Option [Список [(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, результат розуміння буде, звичайно, a List. (Це було для мене новим: адже розуміння не завжди призводить до потоків, навіть не обов’язково до Seqs.)

Тепер погляньте, як 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виклику - це те, що повертає a List, але не an 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 зі списком. Для прикладів інших речей, які можуть бути M, подивіться на підкласи цієї риси .

Навіщо поєднання List[T]з (T) => Option[T]роботою, хоча, коли ви починали зі Списку? У цьому випадку бібліотека використовує більш загальний тип, де це має сенс. Таким чином, ви можете поєднати List з Traversable, і відбувається неявне перетворення з Option в Traversable.

Суть в наступному: подумайте, який тип ви хочете повернути, і почніть із цього типу як першого генератора. При необхідності оберніть його таким типом.


Я б стверджував, що це поганий вибір дизайну, коли регулярний forсинтаксис виконує такий тип функторного / монадичного розсолоджування. Чому б не мати інакше названих методів для відображення функторів / монад, наприклад fmapтощо, та не зарезервувати forсинтаксис, щоб мати надзвичайно просту поведінку, яка відповідає очікуванням практично будь-якої іншої основної мови програмування?
ely,

Ви можете зробити окремі речі fmap / lift такими загальними, як вам подобається, не роблячи загальновизнаного послідовного обчислювального потоку керування потоком даних надзвичайно дивовижним і з різними ускладненнями продуктивності тощо. "Все працює з для" не варто цього.
ely,

4

Можливо, це пов’язано з тим, що Option не є ітератором. Імпліцитний Option.option2Iterableбуде обробляти випадок, коли компілятор очікує, що другий буде 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.