私は最近、HaskellのFunctors、Applicatives、Monadsについて 記事 を読み、これらのステートメントで終わります:
fmap
または<$>
を使用して、ラップされた値に関数を適用します<*>
またはliftA
を使用して、ラップされた関数にラップされた関数を適用します>>=
またはliftM
を使用して、ラップされた値を返す関数をラップされた値に適用しますこれはすべて明らかですが、ラップされた値の目的が何であるかわかりません。 pythonまたはJavaのOptional
のデコレータのように機能しますか?現実世界からの実用的な例を大いに感謝します。
JavaのOptional
は基本的にHaskellのMaybe
モナドと同等であるため、これらの「ラップされた値」がどのように動作するかを理解するための出発点として最適です。
Functors
、Applicatives
およびMonads
は、既存のタイプを変更できる追加の動作をモデリングする方法です。 Maybe
は型を受け取り、1つの単一インスタンスまたはno instanceの両方をモデル化できるように変更しますそのタイプの。 List
は、指定されたタイプのインスタンスのシーケンスをモデル化するようにタイプを変更します。
これらの各型クラスは、ラッピング型による変更を尊重して、特定の関数をラッピング型に適用する方法を提供します。たとえば、Haskellでは、
ファンクタはfmap
または_<$>
_関数を使用します(同じものに対して異なる名前):
fmap
または_<$>
_ Functor f => (a -> b) -> f a -> f b
これは関数を取り、ラップされた要素に適用されます
_fmap (\x -> x + 1) (Just 1) -- Applies (+1) to the inner value, returning (Just 2)
fmap (\x -> x + 1) Nothing -- Applies (+1) to an empty wrapper, returning Nothing
fmap (\x -> x + 1) [1, 2, 3] -- Applies (+1) to all inner values, returning [2, 3, 4]
(\x -> x + 1) <$> [1, 2, 3] -- Same as above
_
アプリケーションは_<*>
_関数を使用します:
<*>
_ Applicative f => f (a -> b) -> f a -> f b
これはwrapped関数を受け取り、ラップされた要素に適用します
_(Just (\x -> x + 1)) <*> (Just 1) -- Returns (Just 2)
(Just (\x -> x + 1)) <*> Nothing -- Returns Nothing
Nothing <*> (Just 1) -- Returns Nothing
[(*2), (*4)] <*> [1, 2] -- Returns [2, 4, 4, 8]
_
Monad型クラスには2つの関連する関数があります:
return
_Monad m => a -> m a
_(>>=)
_ Monad m => m a -> (a -> m b) -> m b
(「バインド」と発音)return
関数は、Cスタイル言語でおなじみの関数と似ていません。ラップされていない生の値を受け取り、それを目的のモナド型にラップします。
_makeJust :: a -> Maybe a
makeJust x = return x
let foo = makeJust 10 -- returns (Just 10)
_
Bind関数を使用すると、モナドの内部要素を一時的にアンラップして、それらを同じモナドにラップアップするアクションを実行する関数に渡すことができます。これはreturn
関数で簡単な場合に使用できます。
_[1, 2, 3, 4] >>= (\x -> return (x + 1)) -- Returns [2, 3, 4, 5]
(Just 1) >>= (\x -> return (x + 1)) -- Returns (Just 2)
Nothing >>= (\x -> return (x + 1)) -- Returns Nothing
_
興味深いのは、return
を使用する必要のない、連鎖する関数がある場合です。 getLine
とputStrLn
を例として使用します。これらには、IO
モナドを使用した次の署名があります。
getLine
_IO String
_putStrLn
String -> IO ()
これらの関数は次のように呼び出すことができます。
_getLine >>= (\x -> putStrLn x) -- Gets a line from IO and prints it to the console
getLine >>= putStrLn -- With currying, this is the same as above
_
_>>=
_関数を使用して、いくつかの操作をチェーンすることもできます。
_-- Reads a line from IO, converts to a number, adds 10 and prints it
getLine >>= (return . read) >>= (return . (+10)) >>= putStrLn . show
_
その記事は非常に視覚的で直感的であるという利点があります...しかし、しばしば誤っている直感を構築することを犠牲にしています。ファンクタ、アプリケーション、モナドの概念では、厳密に言えば、値を「ラップ」する必要はありません。
ここで検討すべき1つの良い例は、最近(特にJavascriptで)勢いを増している約束の概念です。この意味で、promiseは、リモートサービスからデータをフェッチするなど、非同期のバックグラウンド計算の結果値のプレースホルダーとして機能するオブジェクトです。それに加えて、非同期計算と、予想される結果を操作する必要がある関数との間のメディエーターとして機能します。
Promiseライブラリは通常、関数をプロミスに「チェーン」できる関数/モナディックAPIをサポートします。これにより、元のプロミスの結果にその関数を適用した結果を生成する別のプロミスが生成されます。
これは、ファンクター/モナドインターフェースの値の鮮明な例を提供します。なぜなら、Promiseの重要な点は、バックグラウンドタスクの結果にどの関数を適用するかを言うことができるということですそのタスクが完了する前に。 promiseに対して関数をmap
するとき、関数に適用する必要がある値はまだ計算されていない可能性があります(そして実際、どこかにエラーがある場合、それは決して計算されないかもしれません)。
より一般的には、functor/applicative/monadは、関数と引数の間に位置するmediatorオブジェクトのインターフェースと考えることができ、それらをいくつかのポリシーに従って間接的に接続します。関数を使用する最も簡単な方法は、いくつかの引数を指定して呼び出すことです。ただし、ファーストクラスの関数がある場合は、他の間接的なオプションがあります。関数をメディエーターオブジェクトに指定して、関数が呼び出されるかどうか、いつ呼び出されるか、何回呼び出されるか、その結果をどう処理するかを制御できます。約束はそのような例の1つです。
さて、オプションはモナド(そして、したがって、アプリケーションファンクタとファンクタ)であり、実際、多くのコンテナタイプもモナディックであり、通常、コンテナタイプタイプがこれらのものとしてどのように機能するかについて、人々はかなり良い直感を持っています。あなたの質問。
ただし、通常はコンテナーとは考えられない多くのことは、モナド、アプリケーション、またはファンクターである場合もあります。パーサのコンビネータまたはIOまたはログへの書き込み。それらを見る別の方法は、たとえば、あるコンテキストでの計算としてですが、Listは明らかに値のコンテナであり、 非決定論的計算を表すリストモナド
ここまでのところ、モナドは本来の例よりも本質的に抽象的なものです。それらは文字通りです:
3 モナドの法則 を満たす、それ以上の統一原則はありません(モナドの場合、ApplicativeおよびFunctorの関連する関数と法則に置き換えます)