Подтвердить что ты не робот

Невозможно использовать для понимания для отображения списка в будущем

У меня есть эта проблема, с которой я должен работать каждый раз. Я не могу сопоставить что-то, что содержится в будущем, используя для понимания.

Пример:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

val f = Future( List("A", "B", "C") )
for {
  list <- f
  e <- list
} yield (e -> 1)

Это дает мне ошибку:

 error: type mismatch;
 found   : List[(String, Int)]
 required: scala.concurrent.Future[?]
              e <- list
                ^

Но если я это сделаю, он отлично работает:

f.map( _.map( (_ -> 1) ) )

Должен ли я не делать этого, используя для понимания, причина, по которой он работает в моем другом примере, что я не планирую? Я использую Scala 2.10.0.

4b9b3361

Ответ 1

Хорошо, когда у вас есть несколько генераторов в одном для понимания, вы выравниваете полученный тип. То есть вместо получения List[List[T]] вы получаете List[T]:

scala> val list = List(1, 2, 3)
list: List[Int] = List(1, 2, 3)

scala> for (a <- list) yield for (b <- list) yield (a, b)
res0: List[List[(Int, Int)]] = List(List((1,1), (1,2), (1,3)), List((2,1
), (2,2), (2,3)), List((3,1), (3,2), (3,3)))

scala> for (a <- list; b <- list) yield (a, b)
res1: List[(Int, Int)] = List((1,1), (1,2), (1,3), (2,1), (2,2), (2,3),
(3,1), (3,2), (3,3))

Теперь, как бы вы сгладили Future[List[T]]? Это не может быть Future[T], потому что вы будете получать несколько T, а Future (в отличие от List) может хранить только один из них. Аналогичная проблема возникает и с Option, кстати:

scala> for (a <- Some(3); b <- list) yield (a, b)
<console>:9: error: type mismatch;
 found   : List[(Int, Int)]
 required: Option[?]
              for (a <- Some(3); b <- list) yield (a, b)
                                   ^

Самый простой способ - просто вставить несколько для понимания:

scala> for {
     |   list <- f
     | } yield for {
     |   e <- list
     | } yield (e -> 1)
res3: scala.concurrent.Future[List[(String, Int)]] = scala.concurrent.im
[email protected]

В ретроспективе это ограничение должно было быть довольно очевидным. Проблема в том, что почти все примеры используют коллекции, и все коллекции просто GenTraversableOnce, поэтому их можно смешивать свободно. Добавьте к этому, механизм CanBuildFrom, для которого Scala подвергся большой критике, позволяет смешивать в произвольных коллекциях и возвращать конкретные типы вместо GenTraversableOnce.

И, чтобы сделать вещи еще более размытыми, Option можно преобразовать в Iterable, что позволяет комбинировать параметры с коллекциями до тех пор, пока опция не будет первой.

Но основным источником путаницы, на мой взгляд, является то, что никто никогда не упоминает об этом ограничении при обучении пониманию.

Ответ 2

Хм, думаю, я понял. Мне нужно обернуть в будущем, поскольку для понимания добавляется плоская карта.

Это работает:

for {
  list <- f
  e <- Future( list )
} yield (e -> 1)

Когда я добавил выше, я еще не видел ответов. Однако, чтобы расширить это, можно сделать работу в рамках одного осознания. Не уверен, что это стоит накладных расходов в будущем (редактирование: при успешном использовании не должно быть накладных расходов).

for {
  list1 <- f
  list2 <- Future.successful( list1.map( _ -> 1) )
  list3 <- Future.successful( list2.filter( _._2 == 1 ) )
} yield list3

Добавление, полгода спустя.

Другим способом решения этого вопроса является просто использовать назначение = вместо <-, если у вас есть другой тип, кроме исходного типа возврата карты.

При использовании присваивания эта строка не будет отображаться плоской. Теперь вы можете делать явную карту (или другое преобразование), которая возвращает другой тип.

Это полезно, если у вас есть несколько преобразований, где один шаг, который не имеет того же типа возврата, что и другие шаги, но вы все еще хотите использовать синтаксис for-comprehension, потому что он делает код более читаемым.

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future

val f = Future( List("A", "B", "C") )
def longRunning( l:List[(String, Int)] ) = Future.successful( l.map(_._2) )

for {
  list <- f
  e = list.map( _ -> 1 )
  s <- longRunning( e )
} yield s

Ответ 3

Исходная версия не компилируется, потому что List и Future - разные монады. Чтобы понять, почему это проблема, подумайте о том, что это означает:

f.flatMap(list => list.map(e => e -> 1))

Ясно, что list.map(_ -> 1) - это список пар (String, Int), поэтому аргумент нашей flatMap - это функция, которая отображает списки строк в списки этих пар. Но нам нужно что-то, что отображает списки строк в Future. Так что это не компилируется.

Версия в вашем ответе компилируется, но она не делает то, что вы хотите. Это desugars к этому:

f.flatMap(list => Future(list).map(e => e -> 1))

На этот раз типы выстраиваются, но мы не делаем ничего интересного - мы просто вытаскиваем значение из Future, помещая его обратно в Future и сопоставляя результат. Итак, мы получили что-то типа Future[(List[String], Int)], когда нам нужен Future[List[(String, Int)]].

То, что вы делаете, это своего рода операция двойного сопоставления с двумя (разными) вложенными монадами, и это не то, что вам поможет for -понимание. К счастью, f.map(_.map(_ -> 1)) делает именно то, что вам нужно, и ясен и краток.

Ответ 4

Я нахожу эту форму более читаемой, чем последовательная карта или последовательный выход:

for (vs <- future(data);
     xs = for (x <- vs) yield g(x)
) yield xs

за счет карты повторения:

f.map((_, xs)).map(_._2)

или более точно:

f.map((vs: List[Int]) => (vs, for (x <- vs) yield g(x))).map(_._2)