web-dev-qa-db-ja.com

Future内のリストをマッピングするための理解に使用できない

毎回回避しなければならないこの問題があります。 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) ) )

For内包表記を使用してこれを行うことはできないはずですが、フラットマップを使用しない他の例で機能するのはなぜですか? Scala 2.10.0を使用しています。

41
Magnus

まあ、あなたが理解のために単一の複数のジェネレータを持っているとき、あなたはflattening結果の型です。つまり、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]]をどのようにフラット化しますか?複数のTを取得するため、Future[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であるため、自由に混合できることです。これに加えて、Scalaが批判されているCanBuildFromメカニズムにより、GenTraversableOnceの代わりに、任意のコレクションを混合して特定の型を取り戻すことが可能になります。 。

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

しかし、私の意見では、混乱の主な原因は、理解力を教えるときに誰もこの制限に触れたことがないということです。

65

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

これは機能します:

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

上記を追加したところ、まだ回答がありませんでした。ただし、これをさらに詳しく説明するために、1つのfor内包内で作業することができます。ただし、将来のオーバーヘッドの価値があるかどうかはわかりません(編集:成功を使用するとオーバーヘッドは発生しません)。

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

補遺、半年後

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

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

これは、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
8
Magnus

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

_f.flatMap(list => list.map(e => e -> 1))
_

明らかにlist.map(_ -> 1)は_(String, Int)_ペアのリストであるため、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))はユーザーの意図どおりに機能し、明確で簡潔です。

5
Travis Brown

このフォームは、シリアルマップまたはシリアルイールドよりも読みやすくなっています。

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)
1
som-snytt