42

毎回回避しなければならないこの問題があります。for 内包表記を使用して Future に含まれるものをマップすることはできません。

例:

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 を使用しています。

4

4 に答える 4

66

さて、理解のために単一のジェネレーターに複数のジェネレーターがある場合、結果の型をフラット化しています。つまり、 を取得する代わりに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) はそのうちの 1 つしか格納できないため、 にすることはできません。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
pl.Promise$DefaultPromise@4f498585

振り返ってみると、この制限はかなり明白だったはずです。問題は、ほとんどすべてのサンプルがコレクションを使用し、すべてのコレクションが単にGenTraversableOnceであるため、自由に混合できることです。それに加えて、CanBuildFromScala が非常に批判されているメカニズムにより、任意のコレクションを混在させて特定の型を取得することが可能になりますGenTraversableOnce

そして、物事をさらにぼやけさせるために、Optionを に変換することができますIterable。これにより、オプションが最初に来ない限り、オプションをコレクションと組み合わせることができます。

しかし、私の意見では、混乱の主な原因は、理解を教えるときに誰もこの制限について言及していないことです.

于 2013-01-16T02:18:09.033 に答える
8

うーん、わかったと思います。for 内包表記がフラットマップを追加するため、未来にラップする必要があります。

これは機能します:

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

上記を追加したとき、まだ回答がありませんでした。ただし、これを拡張するために、1 つの for-comprehension 内で作業を行うことができます。ただし、Future オーバーヘッドの価値があるかどうかはわかりません (編集: success を使用すると、オーバーヘッドはなくなります)。

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

追記、半年後。

これを解決する別の方法は、最初のマップの戻り値の型とは別の型がある場合に代入を使用する=ことです。<-

割り当てを使用する場合、その行はフラット マップされません。これで、別の型を返す明示的なマップ (またはその他の変換) を自由に実行できるようになりました。

これは、1 つのステップが他のステップと同じ戻り値の型を持たない複数の変換がある場合に役立ちますが、コードが読みやすくなるため、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
于 2013-01-16T00:44:03.650 に答える
5

ListFutureが異なるモナドであるため、元のバージョンはコンパイルされません。これが問題である理由を確認するには、脱糖する目的を考えてください。

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

明らかにペアlist.map(_ -> 1)のリストである(String, Int)ため、our の引数flatMapは、文字列のリストをこれらのペアのリストにマップする関数です。しかし、文字列のリストをFuture何らかの種類にマップするものが必要です。したがって、これはコンパイルされません。

あなたの答えのバージョンはコンパイルされますが、あなたが望むことはしません。これは次のように脱糖します。

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

今回は型が並んでいますが、興味深いことは何もしていません。 から値を取り出してFutureに戻しFuture、結果をマッピングしているだけです。そのため、 .Future[(List[String], Int)]が必要なときにFuture[List[(String, Int)]].

あなたがしているのは、2 つの (異なる) ネストされたモナドを使用した一種の二重マッピング操作であり、それはfor-comprehension が役立つものではありません。幸いなf.map(_.map(_ -> 1))ことに、あなたが望むことを正確に行い、明確かつ簡潔です。

于 2013-01-16T01:26:45.040 に答える
1

この形式は、シリアル マップやシリアル イールドよりも読みやすいと思います。

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)
于 2013-01-16T07:56:51.057 に答える