web-dev-qa-db-ja.com

flatMap / Map変換への理解と混同

私は本当にMapとFlatMapを理解していないようです。私が理解できないのは、理解がmapとflatMapのネストされた呼び出しのシーケンスであるということです。次の例は Scalaの関数型プログラミング からのものです

def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
            f <- mkMatcher(pat)
            g <- mkMatcher(pat2)
 } yield f(s) && g(s)

に変換する

def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = 
         mkMatcher(pat) flatMap (f => 
         mkMatcher(pat2) map (g => f(s) && g(s)))

MkMatcherメソッドは次のように定義されます。

  def mkMatcher(pat:String):Option[String => Boolean] = 
             pattern(pat) map (p => (s:String) => p.matcher(s).matches)

パターンの方法は次のとおりです。

import Java.util.regex._

def pattern(s:String):Option[Pattern] = 
  try {
        Some(Pattern.compile(s))
   }catch{
       case e: PatternSyntaxException => None
   }

誰かがここでmapとflatMapを使用して、背後にある理論的根拠に光を当てることができれば素晴らしいでしょう。

81
sc_ray

私はscalaメガマインドではありませんので、自由に修正してください。しかし、これがflatMap/map/for-comprehensionの物語を自分に説明する方法です!

for comprehensionおよびscala's map / flatMapへの翻訳を理解するには、小さなステップを踏んで、構成要素-mapおよびflatMapを理解する必要があります。しかし、scala's flatMapただmapflattenではありません。もしそうなら、どうしてそんなに多くの開発者がそれやfor-comprehension / flatMap / mapを把握するのが難しいと思うのでしょうか。さて、単にscalaのmapおよびflatMap署名を見ると、同じ戻り値の型M[B]を返し、同じ入力引数A(少なくとも彼らが取る機能の最初の部分)それがそうであれば何が違いを生むのですか?

当社の計画

  1. Scalaのmapを理解してください。
  2. ScalaのflatMapを理解してください。
  3. Scalaのfor comprehensionを理解してください。`

スカラのマップ

scalaマップの署名:

map[B](f: (A) => B): M[B]

しかし、このシグニチャを見ると、大きな部分が欠落しています。これは、このAはどこから来たのでしょうか?コンテナはA型であるため、コンテナのコンテキストでこの関数を見ることが重要です-M[A]。コンテナはList型のアイテムのAであり、map関数はA型の各アイテムをB型に変換する関数を取ります。タイプB(またはM[B])のコンテナーを返します

コンテナを考慮してマップの署名を書きましょう:

M[A]: // We are in M[A] context.
    map[B](f: (A) => B): M[B] // map takes a function which knows to transform A to B and then it bundles them in M[B]

マップに関する非常に非常に重要な事実-出力コンテナM[B]自動がバンドルされていることに注意してください。もう一度強調しましょう。

  1. mapは出力コンテナを選択し、作業するソースと同じコンテナになるため、M[A]コンテナではMに対してのみ同じBコンテナを取得しますM[B]と他には何もありません!
  2. mapはこのコンテナ化を行っており、AからBへのマッピングを指定するだけで、M[B]のボックスに配置されます。

内部アイテムの変換方法を指定したアイテムをcontainerizeする方法を指定しなかったことがわかります。 M[A]M[B]の両方に同じコンテナMがあるので、これはM[B]が同じコンテナであることを意味します。つまり、List[A]があれば、 List[B]、さらに重要なことにmapがあなたのためにやってくれます!

mapを扱ったので、flatMapに進みましょう。

ScalaのflatMap

その署名を見てみましょう:

flatMap[B](f: (A) => M[B]): M[B] // we need to show it how to containerize the A into M[B]

FlatMapのマップからflatMapへの大きな違いを見ると、A to Bから変換するだけでなく、M[B]にコンテナー化する関数が提供されています。

コンテナ化を誰が行うのか?

それでは、map/flatMapへの入力関数がM[B]へのコンテナ化をどうしてそんなに気にするのか、マップ自体がコンテナ化を行うのはなぜですか?

for comprehensionのコンテキストでは、forで提供されるアイテムの複数の変換が行われているため、アセンブリラインの次のワーカーにパッケージを決定する機能を提供しています。各労働者が製品に対して何かをする組立ラインがあり、最後の労働者だけがそれを容器に包装していると想像してください! flatMapへようこそ。これが目的です。mapでは、アイテムの作業が終了すると、各ワーカーもパッケージを作成するため、コンテナを介してコンテナを取得できます。

理解の強さ

次に、上記の内容を考慮して、理解度を調べてみましょう。

def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
    f <- mkMatcher(pat)   
    g <- mkMatcher(pat2)
} yield f(s) && g(s)

ここにあるもの:

  1. mkMatchercontainerを返します。コンテナには関数が含まれます:String => Boolean
  2. ルールは、複数の<-があれば、最後のものを除いてflatMapに変換されます。
  3. f <- mkMatcher(pat)sequenceの最初にあるので(Assembly lineと考えてください)、必要なのはfを取得し、Assemblyラインの次のワーカーに渡すことだけです。組立ラインの次の作業者(次の機能)にアイテムのパッケージングが何であるかを判断できるようにします。これが最後の機能がmapである理由です。
  4. 最後のg <- mkMatcher(pat2)mapを使用します。これは、アセンブリラインの最後だからです!そのため、map( g =>で最終操作を実行できます。 gを引き出し、fによって既にコンテナから引き出されているflatMapを使用します。したがって、最初になります。

    mkMatcher(pat)flatMap(f // f関数を引き出して、次のAssemblyラインワーカーにアイテムを与えます(fにアクセスできることを確認し、それをパッケージ化しないでください。アセンブリラインワーカーがコンテナを決定しますmkMatcher(pat2)map(g => f(s) ...))//これがアセンブリラインの最後の関数であるためmapを使用してgをコンテナから取り出し、パッケージに戻します。そのmapとこのパッケージはずっとスロットルされ、パッケージまたはコンテナになります。

5
Tomer Ben David

理論的根拠は、利益として適切な「フェイルファースト」エラー処理を提供するモナド演算を連鎖させることです。

実際には非常に簡単です。 mkMatcherメソッドは、Option(Monad)を返します。モナド演算であるmkMatcherの結果は、NoneまたはSome(x)のいずれかです。

mapまたはflatMap関数をNoneに適用すると、常にNoneが返されます。パラメーターとしてmapおよびflatMapに渡された関数は評価されません。

したがって、あなたの例では、mkMatcher(pat)がNoneを返すと、それに適用されるflatMapはNoneを返し(2番目のモナド演算mkMatcher(pat2)は実行されません)、最後のmapは再びNone。つまり、for内の操作のいずれかがNoneを返す場合、フェールファースト動作が発生し、残りの操作は実行されません。

これは、エラー処理の単項スタイルです。命令型スタイルは、基本的に(catch節への)ジャンプである例外を使用します

最後の注意:patterns関数は、命令型スタイルのエラー処理(try...catch)を、Optionを使用したモナドスタイルのエラー処理に「変換」する典型的な方法です。

4
Bruno Grieder

これは次のように分類できます。

def bothMatch(pat:String,pat2:String,s:String):Option[Boolean] = for {
    f <- mkMatcher(pat)  // for every element from this [list, array,Tuple]
    g <- mkMatcher(pat2) // iterate through every iteration of pat
} yield f(s) && g(s)

これを実行して、どのように展開されるかをよりよく理解します

def match items(pat:List[Int] ,pat2:List[Char]):Unit = for {
        f <- pat
        g <- pat2
} println(f +"->"+g)

bothMatch( (1 to 9).toList, ('a' to 'i').toList)

結果は次のとおりです。

1 -> a
1 -> b
1 -> c
...
2 -> a
2 -> b
...

これはflatMapに似ています-patの各要素とforeach要素map itをpat2の各要素にループします

1
korefn

まず、mkMatcherは、pattern関数に示されているように、シグネチャが_String => Boolean_である関数を返します。これは、通常のJavaプロシージャPattern.compile(string)を実行するだけですこの行で

_pattern(pat) map (p => (s:String) => p.matcher(s).matches)
_

map関数はpatternの結果、つまり_Option[Pattern]_に適用されるため、_p => xxx_のpはコンパイルしたパターンにすぎません。したがって、パターンpを指定すると、ストリングsを取り、パターン__ sが一致するかどうかを確認する新しい関数が作成されます。

_(s: String) => p.matcher(s).matches
_

p変数はコンパイルされたパターンにバインドされていることに注意してください。これで、mkMatcherによって署名_String => Boolean_を持つ関数がどのように構築されるかが明確になりました。

次に、bothMatchに基づくmkMatcher関数をチェックアウトします。 bothMathchの仕組みを示すために、最初にこの部分を見てみましょう。

_mkMatcher(pat2) map (g => f(s) && g(s))
_

このコンテキストではmkMatcherであるgからシグネチャ_String => Boolean_を持つ関数を取得したため、g(s)Pattern.compile(pat2).macher(s).matchesと同等です。これは、Stringがパターン_pat2_。 f(s)はどうでしょうか。g(s)と同じですが、唯一の違いは、mkMatcherの最初の呼び出しではflatMapの代わりにmapを使用することです。 mkMatcher(pat2) map (g => ....)は_Option[Boolean]_を返すため、両方の呼び出しにmapを使用すると、ネストされた結果_Option[Option[Boolean]]_を取得します。

0
xiaowl