11

私はしばらくの間、FP 言語 (オンとオフ) を調べており、Scala、Haskell、F# などで遊んできました。私は見たものが好きで、FP の基本的な概念のいくつかを理解しています (圏論のバックグラウンドはまったくないので、数学については話さないでください)。

したがって、関数を取り、を返す型M[A]が与えられます。しかし、関数を受け取って を返すものもあります。また、which を取り、 aを返すものもあります。mapA=>BM[B]flatMapA=>M[B]M[B]flattenM[M[A]]M[A]

さらに、私が読んだソースの多くは、次のように説明flatMapmapていflattenます。

では、flatMapが と同等のように思われる場合flatten compose map、その目的は何ですか? この質問は実際には Scala 固有のものではないため、「理解のために」サポートするとは言わないでください。そして、私はその背後にある概念よりも、構文糖衣に関心がありません。Haskell のバインド演算子 ( >>=) についても同じ疑問が生じます。どちらも圏論の概念に関連していると思いますが、私はその言語を話せません。

私は Brian Beckman の素晴らしいビデオDon't Fear the Monadを 2 回以上見たことがあります。これがモナド構成演算子であることがわかると思いますflatMapが、彼がこの演算子を説明する方法で使用されているのを見たことがありません。この機能を実行しますか?もしそうなら、その概念をどのようにマッピングしflatMapますか?

ところで、私はこの質問について長い記事を書き、意味を理解するために実行した実験を示す多くのリストを作成し、この質問に遭遇してflatMapの質問のいくつかに答えました。ときどき、私は Scala の暗黙が嫌いです。彼らは本当に水を濁らせることができます。:)

4

5 に答える 5

31

他のいくつかの言語では「バインド」として知られる FlatMap は、関数合成についてあなたが言ったとおりです。

次のような関数があると想像してみてください。

def foo(x: Int): Option[Int] = Some(x + 2)
def bar(x: Int): Option[Int] = Some(x * 3)

関数はうまく機能し、 return を呼び出し、 returnを呼び出し、私たちは皆満足していますfoo(3)Some(5)bar(3)Some(9)

しかし、この操作を複数回行う必要がある状況に遭遇しました。

foo(3).map(x => foo(x)) // or just foo(3).map(foo) for short

仕事は終わりましたよね?

そうでないことを除いて。上記の式の出力はSome(Some(7)), ではなくであり、最後に別のマップをチェーンしSome(7)たい場合はできません。foobarIntOption[Int]

入るflatMap

foo(3).flatMap(foo)

戻りますSome(7)、そして

foo(3).flatMap(foo).flatMap(bar)

を返しますSome(15)

これは素晴らしい!を使用flatMapすると、形状の関数A => M[B]をオブリビオンに連鎖させることができます (前の例ではAand Bare Int、 and Mis Option)。

より技術的に言えば; つまりflatMap、、、またはなどの「ラップされた」値を取り、前述のおよびなどの通常はラップされていない値を取る関数にそれを押し込みます。これは、最初に値を「アンラップ」してから関数に渡すことによって行われます。bindM[A] => (A => M[B]) => M[B]Some(3)Right('foo)List(1,2,3)foobar

これにボックスの類推が使用されているのを見たことがあるので、専門的に描いた MSPaint の図を見てください。 ここに画像の説明を入力

このアンラップと再ラップの動作は、 を返さない 3 番目の関数を導入してシーケンスにそれOption[Int]を試みた場合、モナド (この場合は)を返すことを期待しているため、機能しないことを意味します。flatMap flatMapOption

def baz(x: Int): String = x + " is a number"

foo(3).flatMap(foo).flatMap(bar).flatMap(baz) // <<< ERROR

これを回避するには、関数がモナドを返さない場合は、通常のmap関数を使用するだけです

foo(3).flatMap(foo).flatMap(bar).map(baz)

その後、どちらが返されますかSome("15 is a number")

于 2015-12-12T10:05:32.510 に答える
2

これは、何かを行うために複数の方法を提供するのと同じ理由です。これは、ラップしたい場合がある十分に一般的な操作です。

反対の質問をすることもできます:コレクション内に 1 つの要素を格納する方法があるのはmapなぜですか? あれは、flattenflatMap

x map f
x filter p

で置き換えることができます

x flatMap ( xi => x.take(0) :+ f(xi) )
x flatMap ( xi => if (p(xi)) x.take(0) :+ xi else x.take(0) )

では、なぜ と を気にするmapfilterでしょうか。

実際、他の多くの操作を再構築するために必要な最小限の操作セットがいくつかあります (flatMap柔軟性があるため、 を選択することをお勧めします)。

実用的には、必要なツールを持っている方がよいでしょう。調整不可能なレンチがあるのと同じ理由です。

于 2015-12-12T01:26:24.217 に答える
1

「flatMap」または「bind」メソッドは、Monadic コンストラクト ( ListOption、または などFuture) でラップされた出力を提供するメソッドを連鎖させるための非常に貴重な方法を提供します。たとえばFuture、結果を生成する 2 つのメソッドがあるとします (たとえば、これらのメソッドはデータベースや Web サービス呼び出しなどへの長時間実行呼び出しを行い、非同期で使用する必要があります)。

def fn1(input1: A): Future[B]  // (for some types A and B)

def fn2(input2: B): Future[C]  // (for some types B and C)

これらを組み合わせる方法は?を使用flatMapすると、これを次のように簡単に実行できます。

def fn3(input3: A): Future[C] = fn1(a).flatMap(b => fn2(b))

この意味で、関数を から を使用して「構成」しましたfn3fn1これfn2flatMap、同じ一般的な構造を持っています (したがって、さらに同様の関数で構成することができます)。

このmapメソッドは、あまり便利ではなく、簡単にチェーン化できない を提供しFuture[Future[C]]ます。確かにflatten、これを減らすために を使用できますが、flatMapメソッドは 1 回の呼び出しでそれを行い、必要なだけチェーンすることができます。

実際、これは非常に便利な作業方法であり、Scala は基本的にこれのショートカットとして for-comprehension を提供しています (Haskell もバインド操作のチェーンを簡単に記述する方法を提供しています - 私はそうではありませんただし、Haskell の専門家であり、詳細を思い出せません) - したがって、for 内包表記が一連の呼び出しに「脱糖」されることについて出くわすことになる話ですflatMap(可能性のあるfilter呼び出しと の最終map呼び出しとともにyield) .

于 2015-12-12T07:31:10.713 に答える
0

まあ、どちらも必要ないと主張することができ.flattenます。次のようなことをしないのはなぜですか

@tailrec
def flatten[T](in: Seq[Seq[T], out: Seq[T] = Nil): Seq[T] = in match {
   case Nil => out
   case head ::tail => flatten(tail, out ++ head)
}

マップについても同じことが言えます:

@tailrec
def map[A,B](in: Seq[A], out: Seq[B] = Nil)(f: A => B): Seq[B] = in match {
   case Nil => out
   case head :: tail => map(tail, out :+ f(head))(f)
  } 

では、なぜライブラリによって提供されているのでしょうか.flatten.map同じ理由.flatMapは、利便性です。

もありますが.collect、これは本当に

list.filter(f.isDefinedAt _).map(f)

.reduce実際にはそれ以上のものlist.foldLeft(list.head)(f).headOptionはありません

list match {
    case Nil => None
    case head :: _ => Some(head)
}

など...

于 2015-12-12T02:20:17.587 に答える