38

さて、私はHaskellプログラマーではありませんが、Haskellの背後にある多くのアイデアに絶対に興味を持っており、それを学ぶことを検討しています。しかし、私は正方で立ち往生しています。かなり基本的なように見えるモナドに頭を包むことができないようです。私は、モナドを説明するように求めるSOに関する100万の質問があることを知っているので、私を悩ませているものについてもう少し具体的に説明します。

この素晴らしい記事(Javascriptの紹介)を読んで、モナドを完全に理解したと思いました。それから私はモナドのウィキペディアのエントリを読んで、これを見ました:

ポリモーフィック型(M t)→(t→M u)→(M u)の結合演算。これは、Haskellが中置演算子>>=で表します。その最初の引数はモナディック型の値であり、その2番目の引数は最初の引数の基になる型から別のモナディック型にマップする関数であり、その結果は他のモナディック型になります。

さて、私が引用した記事では、bindは引数を1つだけ取る関数でした。ウィキペディアは2つ言っています。モナドについて私が理解したと思ったのは次のとおりです。

  1. モナドの目的は、さまざまな入力タイプと出力タイプを持つ関数を取得し、それを構成可能にすることです。これは、入力型と出力型を単一のモナディック型でラップすることによって行われます。
  2. モナドは、バインドとユニットという2つの相互に関連する関数で構成されています。Bindは、構成不可能な関数fを受け取り、入力としてモナディック型を受け入れ、モナディック型を返す新しい関数gを返します。gは構成可能です。unit関数は、fが期待する型の引数を取り、それをモナディック型でラップします。次に、これをgに渡すか、gのような関数の任意の合成に渡すことができます。

しかし、バインドの概念には関数という1つの引数が必要なので、何か問題があるはずです。しかし(ウィキペディアによると)Haskellのバインドは実際には2つの引数を取ります!私の間違いはどこにありますか?

4

3 に答える 3

29

あなたは間違いを犯していません。ここで理解する重要なアイデアはカリー化です-2つの引数のHaskell関数は2つの方法で見ることができます。1つ目は、単純に2つの引数の関数です。たとえば、がある場合(+)、これは通常、2つの引数を取り、それらを追加するものと見なされます。それを見る別の方法は、追加のマシンプロデューサーとしてです。(+)は、たとえば、数を取り、xを追加する関数を作成する関数ですx

(+) x = \y -> x + y
(+) x y = (\y -> x + y) y = x + y

モナドを扱うときは、前述のように=<<、の反転バージョンを考える方がおそらく良い場合があり>>=ます。これを確認する方法は2つあります。

(=<<) :: (a -> m b) -> m a -> m b

これは2つの引数の関数であり、

(=<<) :: (a -> m b) -> (m a -> m b)

これは、前述の記事のように、入力関数を簡単に作成できるバージョンに変換します。(+)これらは、前に説明したのと同じです。

于 2011-11-02T02:29:30.927 に答える
22

モナドについてのあなたの信念を壊させてください。私が失礼なことをしようとしているのではないことをあなたが理解してくれることを心から願っています。私は単に言葉を細かく切り刻むのを避けようとしています。

モナドの目的は、さまざまな入力タイプと出力タイプを持つ関数を取得し、それを構成可能にすることです。これは、入力型と出力型を単一のモナディック型でラップすることによって行われます。

ではない正確に。「モナドの目的」で文を始めるとき、あなたはすでに間違った立場にいます。モナドは必ずしも「目的」を持っているわけではありません。Monadは単なる抽象化であり、特定のタイプに適用され、他のタイプには適用されない分類です。抽象化の目的は、Monad単にそれ、抽象化です。

モナドは、バインドとユニットという2つの相互に関連する関数で構成されています。

はいといいえ。モナドを定義するには、との組み合わせで十分ですが、、、の組み合わせでもbind同様に十分です。後者は、実際、モナドが圏論で一般的に説明される方法です。unitjoinfmapunit

Bindは、構成不可能な関数fを受け取り、入力としてモナディック型を受け入れ、モナディック型を返す新しい関数gを返します。

繰り返しますが、正確ではありません。モナディック関数f :: a -> m bは、特定のタイプで完全に構成可能です。get関数を使用して事後合成することも、g :: m b -> cget関数を使用g . f :: a -> cして事前合成することもできます。h :: c -> af . h :: c -> m b

しかし、2番目の部分は完全に正しいです:(>>= f) :: m a -> m b。他の人が指摘しているように、Haskellのbind関数は逆の順序で引数を取ります。

gは構成可能です。

はい、そうです。の場合g :: m a -> m b、get関数を使用して事前に作成するか、 f :: c -> m aget関数を使用g . f :: c -> m bして事後作成することができます。がモナドである形式である可能性があることに注意してください。「コンポーザブル」とは、「この形の関数のチェーンを任意に長く作れる」という意味だと思いますが、これは一種の真実です。h :: m b -> ch . g :: m a -> cc m vm

unit関数は、fが期待する型の引数を取り、それをモナディック型でラップします。

回りくどい言い方ですが、そうです、その通りです。

この[unitある値に適用した結果]は、g、またはgのような関数の任意の合成に渡すことができます。

繰り返しますが、はい。一般に、Haskellを呼び出すunit(またはHaskellでreturn)のは慣用的なHaskellではありませんが、それをに渡し(>>= f)ます。

-- instead of
return x >>= f >>= g
-- simply go with
f x >>= g

-- instead of
\x -> return x >>= f >>= g
-- simply go with
f >=> g
-- or
g <=< f
于 2011-11-02T06:47:09.333 に答える
11

リンクする記事は、バインドの定義を反転させたsigfpeの記事に基づいています。

まず、定義を反転してbind「バインド」という単語として記述しましたが、通常は演算子として記述します>>=。したがってbind f x、通常は。と記述されx >>= fます。

したがって、Haskellbindはモナドで囲まれた値を取り、関数を返します。関数は関数を受け取り、抽出された値でそれを呼び出します。私は不正確な用語を使用している可能性があるので、コードの方が良いかもしれません。

あなたが持っている:

sine x = (sin x,     "sine was called.")
cube x = (x * x * x, "cube was called.")

ここで、JSバインドを変換します(Haskellは自動カリー化を行うため、呼び出しbind fはタプルを受け取る関数を返し、パターンマッチングがそれをアンパックしxs理解できることを願っています):

bind f (x, s) = (y, s ++ t)
                where (y, t) = f x

あなたはそれが機能しているのを見ることができます:

*Main> :t sine
sine :: Floating t => t -> (t, [Char])
*Main> :t bind sine
bind sine :: Floating t1 => (t1, [Char]) -> (t1, [Char])
*Main> (bind sine . bind cube) (3, "")
(0.956375928404503,"cube was called.sine was called.")

bindさて、 :の引数を逆にしましょう。

bind' (x, s) f = (y, s ++ t)
                 where (y, t) = f x

まだ同じことをしていることがはっきりとわかりますが、構文が少し異なります。

*Main> bind' (bind' (3, "") cube) sine
(0.956375928404503,"cube was called.sine was called.")

現在、Haskellには、任意の関数を中置演算子として使用できる構文トリックがあります。だからあなたは書くことができます:

*Main> (3, "") `bind'` cube `bind'` sine
(0.956375928404503,"cube was called.sine was called.")

( )に名前bind'を変更すると、探していたものが得られます。ご覧のとおり、この定義を使用すると、個別の合成演算子を効果的に取り除くことができます。>>=(3, "") >>= cube >>= sine

新しいものをJavaScriptに戻すと、次のようになります(ここでも、引数の順序を逆にするだけです)。

var bind = function(tuple) {
    return function(f) {
        var x  = tuple[0],
            s  = tuple[1],
            fx = f(x),
            y  = fx[0],
            t  = fx[1];

        return [y, s + t];
    };
};

// ugly, but it's JS, after all
var f = function(x) { return bind(bind(x)(cube))(sine); }

f([3, ""]); // [0.956375928404503, "cube was called.sine was called."]

これがお役に立てば幸いですが、混乱が生じることはありません。重要なのは、これら2つのバインド定義は同等であり、呼び出し構文が異なるだけであるということです。

于 2011-11-02T02:58:54.643 に答える