web-dev-qa-db-ja.com

Haskellで値をラップする目的は何ですか?

私は最近、HaskellのFunctors、Applicatives、Monadsについて 記事 を読み、これらのステートメントで終わります:

  • ファンクタ:fmapまたは<$>を使用して、ラップされた値に関数を適用します
  • アプリケーション:<*>またはliftAを使用して、ラップされた関数にラップされた関数を適用します
  • モナド:>>=またはliftMを使用して、ラップされた値を返す関数をラップされた値に適用します

これはすべて明らかですが、ラップされた値の目的が何であるかわかりません。 pythonまたはJavaのOptionalのデコレータのように機能しますか?現実世界からの実用的な例を大いに感謝します。

6
Adam Arold

JavaのOptionalは基本的にHaskellのMaybeモナドと同等であるため、これらの「ラップされた値」がどのように動作するかを理解するための出発点として最適です。

FunctorsApplicativesおよび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を使用する必要のない、連鎖する関数がある場合です。 getLineputStrLnを例として使用します。これらには、IOモナドを使用した次の署名があります。

  • getLine _IO String_
  • putStrLnString -> 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
_
8
KChaloux

その記事は非常に視覚的で直感的であるという利点があります...しかし、しばしば誤っている直感を構築することを犠牲にしています。ファンクタ、アプリケーション、モナドの概念では、厳密に言えば、値を「ラップ」する必要はありません。

ここで検討すべき1つの良い例は、最近(特にJavascriptで)勢いを増している約束の概念です。この意味で、promiseは、リモートサービスからデータをフェッチするなど、非同期のバックグラウンド計算の結果値のプレースホルダーとして機能するオブジェクトです。それに加えて、非同期計算と、予想される結果を操作する必要がある関数との間のメディエーターとして機能します。

Promiseライブラリは通常、関数をプロミスに「チェーン」できる関数/モナディックAPIをサポートします。これにより、元のプロミスの結果にその関数を適用した結果を生成する別のプロミスが生成されます。

これは、ファンクター/モナドインターフェースの値の鮮明な例を提供します。なぜなら、Promiseの重要な点は、バックグラウンドタスクの結果にどの関数を適用するかを言うことができるということですそのタスクが完了する前に。 promiseに対して関数をmapするとき、関数に適用する必要がある値はまだ計算されていない可能性があります(そして実際、どこかにエラーがある場合、それは決して計算されないかもしれません)。

より一般的には、functor/applicative/monadは、関数と引数の間に位置するmediatorオブジェクトのインターフェースと考えることができ、それらをいくつかのポリシーに従って間接的に接続します。関数を使用する最も簡単な方法は、いくつかの引数を指定して呼び出すことです。ただし、ファーストクラスの関数がある場合は、他の間接的なオプションがあります。関数をメディエーターオブジェクトに指定して、関数が呼び出されるかどうか、いつ呼び出されるか、何回呼び出されるか、その結果をどう処理するかを制御できます。約束はそのような例の1つです。

  1. バックグラウンドタスクの結果が完了すると、提供された関数を呼び出します
  2. これらの関数の結果は、それらを待っている他のpromiseに渡されます。
3
sacundim

さて、オプションはモナド(そして、したがって、アプリケーションファンクタとファンクタ)であり、実際、多くのコンテナタイプもモナディックであり、通常、コンテナタイプタイプがこれらのものとしてどのように機能するかについて、人々はかなり良い直感を持っています。あなたの質問。

ただし、通常はコンテナーとは考えられない多くのことは、モナド、アプリケーション、またはファンクターである場合もあります。パーサのコンビネータまたはIOまたはログへの書き込み。それらを見る別の方法は、たとえば、あるコンテキストでの計算としてですが、Listは明らかに値のコンテナであり、 非決定論的計算を表すリストモナド

ここまでのところ、モナドは本来の例よりも本質的に抽象的なものです。それらは文字通りです:

  • 型コンストラクタ
  • 戻り/ユニット関数
  • バインド(または結合とfmap)関数

3 モナドの法則 を満たす、それ以上の統一原則はありません(モナドの場合、ApplicativeおよびFunctorの関連する関数と法則に置き換えます)

2
jk.