私は本当に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を使用して、背後にある理論的根拠に光を当てることができれば素晴らしいでしょう。
私はscalaメガマインドではありませんので、自由に修正してください。しかし、これがflatMap/map/for-comprehension
の物語を自分に説明する方法です!
for comprehension
およびscala's map / flatMap
への翻訳を理解するには、小さなステップを踏んで、構成要素-map
およびflatMap
を理解する必要があります。しかし、scala's flatMap
ただmap
でflatten
ではありません。もしそうなら、どうしてそんなに多くの開発者がそれやfor-comprehension / flatMap / map
を把握するのが難しいと思うのでしょうか。さて、単にscalaのmap
およびflatMap
署名を見ると、同じ戻り値の型M[B]
を返し、同じ入力引数A
(少なくとも彼らが取る機能の最初の部分)それがそうであれば何が違いを生むのですか?
当社の計画
map
を理解してください。flatMap
を理解してください。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]
に自動がバンドルされていることに注意してください。もう一度強調しましょう。
map
は出力コンテナを選択し、作業するソースと同じコンテナになるため、M[A]
コンテナではM
に対してのみ同じB
コンテナを取得しますM[B]
と他には何もありません!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)
ここにあるもの:
mkMatcher
はcontainer
を返します。コンテナには関数が含まれます:String => Boolean
<-
があれば、最後のものを除いてflatMap
に変換されます。f <- mkMatcher(pat)
はsequence
の最初にあるので(Assembly line
と考えてください)、必要なのはf
を取得し、Assemblyラインの次のワーカーに渡すことだけです。組立ラインの次の作業者(次の機能)にアイテムのパッケージングが何であるかを判断できるようにします。これが最後の機能がmap
である理由です。最後の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
とこのパッケージはずっとスロットルされ、パッケージまたはコンテナになります。
理論的根拠は、利益として適切な「フェイルファースト」エラー処理を提供するモナド演算を連鎖させることです。
実際には非常に簡単です。 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
を使用したモナドスタイルのエラー処理に「変換」する典型的な方法です。
これは次のように分類できます。
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
の各要素にループします
まず、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]]
_を取得します。