7

2つのモナディック関数があるとしましょう。

  f :: a -> m b
  g :: b -> m c
  h :: a -> m c

バインド関数は次のように定義されます

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

私の質問は、なぜ私たちは以下のようなことをすることができないのかということです。モナディック値を取り、別のモナディック値を返す関数を宣言しますか?

  f :: a -> m b
  g :: m b -> m c
  h :: a -> m c

バインド関数は次のように定義されます

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

関数が引数としてモナディック値を取ることを制限するハスケルには何がありますか?

編集:私は私の質問を明確にしなかったと思います。重要なのは、バインド演算子を使用して関数を作成している場合、バインド演算子の2番目の引数が非モナディック値(b)をとる関数であるのはなぜですか?なぜそれは一項値()を取り、mb返すことができないのですかmc 。それは、モナドを扱っているときに、作成する関数は常に次のタイプになるということですか。

  f :: a -> m b
  g :: b -> m c
  h :: a -> m c

h = f 'compose' g

私はモナドを学ぼうとしていますが、これは理解できません。

4

9 に答える 9

7

の重要な機能は、タイプMonadの「内部を見て」 、 ;を確認することです。ただし、の重要な制限は、モナドが「不可避」である可能性がなければならないことです。つまり、型クラス操作は、型の関数を記述するのに十分であってはなりません。 まさにこの能力をあなたに与えます。m aaMonadMonadMonad m => m a -> a(>>=) :: Monad m => m a -> (a -> m b) -> m b

しかし、それを達成する方法は複数あります。Monadクラスは次のように定義できます。

class Functor f where
    fmap :: (a -> b) -> f a -> f b

class Functor f => Monad m where
    return :: a -> m a
    join :: m (m a) -> m a

なぜ私たちが機能を持てなかったのかとあなたは尋ねMonad m => m a -> (m a -> m b) -> m bます。まあ、与えられたf :: a -> bfmap f :: ma -> mb基本的にそれです。しかしfmap、それだけでは「中を見る」Monad m => m aことはできませんが、そこから逃れることはできません。しかしjoinfmap一緒にあなたにその能力を与えます。 とで一般的に(>>=)書くことができます:fmapjoin

(>>=) :: Monad m => m a -> (a -> m b) -> m b
ma >>= f = join (fmap f ma)

実際、これは、の定義を思い付くのにMonad問題があるときにインスタンスを定義するための一般的なトリックです—モナドになる関数を記述してから、の一般的な定義を使用します。(>>=)join(>>=)


ええと、それは「いいえ」で質問の「それがそうである必要があるか」の部分に答えます。しかし、なぜそれがそうであるのですか?

Haskellの設計者について話すことはできませんが、私はそれを次のように考えるのが好きです。Haskellのモナディックプログラミングでは、基本的な構成要素は次のようなアクションです。

getLine :: IO String
putStrLn :: String -> IO ()

より一般的には、これらの基本的な構成要素には、、、、、 ...、のMonad m => m aようMonad m => a -> m bなタイプがあります。人々は非公式にこれらの行動を呼びます。 は引数なしのアクション、は1つの引数のアクションなどです。Monad m => a -> b -> m cMonad m => a -> b -> ... -> m zMonad m => m aMonad m => a -> m b

さて、(>>=) :: Monad m => m a -> (a -> m b) -> m b基本的には2つのアクションを「接続」する最も単純な関数です。 getLine >>= putStrLnは、最初に実行getLineし、次に実行putStrLnして、実行から得られた結果を渡すアクションですgetLine。あなたが持っていてfmap、持っていjoinなかった場合、あなたはこれを書かなければならない>>=でしょう:

join (fmap putStrLn getLine)

さらに一般的には(>>=)、アクションの「パイプライン」のような概念を具体化するため、一種のプログラミング言語としてモナドを使用するためのより便利な演算子です。


Control.Monad最後に、モジュールを認識していることを確認してください。return(>>=)はモナドの基本関数ですが、これら2つを使用して定義できる高レベルの関数は他にも無限にあり、そのモジュールはより一般的な関数を数十個収集します。(>>=)あなたのコードは;によって拘束衣に強制されるべきではありません。これは、それ自体で、およびより大きなビルディングブロックのコンポーネントとして役立つ重要なビルディングブロックです。

于 2012-08-15T16:53:10.180 に答える
5

なぜ私たちは以下のようなことをすることができないのですか?モナディック値を取り、別のモナディック値を返す関数を宣言しますか?

f :: a -> m b
g :: m b -> m c
h :: a -> m c

あなたが次のように書きたいと思っていることを私は理解していますか?

compose :: (a -> m b) -> (m b -> m c) -> (a -> m c)
compose f g = h where
  h = ???

これは単なる通常の関数合成ですが、引数の順序が逆になっていることがわかります

(.) :: (y -> z) -> (x -> y) -> (x -> z)
(g . f) = \x -> g (f x)

(.)タイプ、、、およびに特化するx = aことy = m bを選択しましょうz = m c

(.) :: (m b -> m c) -> (a -> m b) -> (a -> m c)

次に、入力の順序を反転すると、目的のcompose関数が得られます

compose :: (a -> m b) -> (m b -> m c) -> (a -> m c)
compose = flip (.)

ここではモナドについても言及していないことに注意してください。mこれは、モナドであるかどうかに関係なく、あらゆる型構築子に対して完全にうまく機能します。


次に、他の質問について考えてみましょう。次のように記述したいとします。

composeM :: (a -> m b) -> (b -> m c) -> (a -> m c)

止まる。フーグルタイム。その型署名を調べてみると、完全に一致していることがわかります。これは>=>Control.Monadからのものですが、この関数ではモナドでm なければならないことに注意してください。

さて、問題はその理由です。この構成が他の構成と異なる点は、この構成がモナドである必要mがあるのに対し、他の構成はそうではないということです。さて、その質問への答えは、モナドの抽象化が何であるかを理解することの中心にあるので、この主題について話すさまざまなインターネットリソースに、より詳細な答えを残しておきます。について何かcomposeMを知らずに書く方法はないと言えば十分です。どうぞ、お試しください。何であるかについての追加の知識がなければ、それを書くことはできません。この関数を書くために必要な追加の知識は、たまたま、mmmMonad

于 2012-08-15T16:23:25.477 に答える
4

あなたの質問を少し言い換えさせてください:

モナドでタイプの関数を使用できない のはなぜですか?g :: m a -> m b

答えは、Functorsを使用してすでに行っています。どこに特に「モナディック」なものはありません。モナドはファンクターです。古き良きものを使用するだけでそのような関数を取得できます:fmap f :: Functor m => m a -> m bf :: a -> bfmap

class Functor f a where
    fmap :: (a -> b) -> f a -> f b
于 2012-08-16T13:10:29.020 に答える
3

2つの関数f :: m a -> m bと1つのモナディック値x :: m aがある場合は、を適用するだけf xです。そのために特別なモナディック演算子は必要ありません。関数適用だけです。ただし、などの関数fは、タイプの値を「見る」ことはできませんa

関数のモナディック合成ははるかに強力な概念であり、型の関数はa -> m bモナディック計算の中核です。モナディック値がx :: m aある場合、タイプの値を取得するために「それに入る」ことはできませんa。ただし、f :: a -> m bタイプの値を操作する関数がある場合aは、を使用して関数を使用して値を作成でき>>=ますx >>= f :: m b。重要なのは、f型の値を「認識」し、それを処理できるaことです(ただし、それを返すことはできず、別の単項値のみを返すことができます)。これはの利点で>>=あり、各モナドは適切な実装を提供する必要があります。

2つの概念を比較するには:

  • を持っている場合はg :: m a -> m b、それをで作成しreturnて取得するg . return :: a -> m b(そしてで作業する>>=)ことができますが、
  • 逆ではありません。m a -> m b一般に、タイプの関数からタイプの関数を作成する方法はありませんa -> m b

したがって、のようなタイプの関数を作成することa -> m bは、のようなタイプの関数を作成するよりも厳密に強力な概念ですm a -> m b


例:リストモナドは、0の回答を含む、可変数の回答を与えることができる計算を表します(非決定論的計算と見なすことができます)。リストモナド内の計算の重要な要素は、タイプの関数ですa -> [b]。それらはいくつかの入力を受け取り、可変数の回答を生成します。これらの関数の構成は、最初の関数から結果を取得し、2番目の関数を各結果に適用し、それをすべての可能な回答の単一のリストにマージします。

タイプの関数は[a] -> [b]異なります。それらは、複数の入力を受け取り、複数の回答を生成する計算を表します。それらを組み合わせることもできますが、元のコンセプトよりも弱いものが得られます。


おそらくさらに特徴的な例はIOモナドです。getChar :: IO Charタイプの関数のみを呼び出して使用した場合IO a -> IO b、読み取られた文字を操作することはできません。しかし、そのような値を、文字を「見て」、それを使って何かを行うことができる>>=型の関数と組み合わせることができます。a -> IO b

于 2012-08-15T19:30:00.330 に答える
1

(>> =)の2番目の引数がモナドを入力として受け取らない理由は、そのような関数をバインドする必要がまったくないためです。適用するだけです:

m :: m a
f :: a -> m b
g :: m b -> m c
h :: c -> m b

(g (m >>= f)) >>= h

gには(>> =)はまったく必要ありません。

于 2012-08-15T15:30:12.037 に答える
1

他の人が指摘しているように、関数が引数としてモナディック値を取ることを制限するものは何もありません。bind関数自体は1つを取りますが、bindに与えられた関数は取りません。

「モナドはコンテナ」という比喩で、これを自分で理解できるようになると思います。この良い例は多分です。多分conatinerから値をアンラップする方法は知っていますが、すべてのモナドについてそれを知っているわけではなく、一部のモナド(IOなど)では完全に不可能です。モナドは、あなたが知る必要のない方法でこれを舞台裏で行うという考えになりました。たとえば、実際にはIOモナドで返された値を操作する必要がありますが、それをアンラップすることはできないため、これを行う関数はIOモナド自体にある必要があります。

于 2012-08-15T11:01:22.627 に答える
1

私はモナドを特定のコンテキストでプログラムを構築するためのレシピと考えるのが好きです。モナドが提供する力は、構築されたプログラム内の任意の段階で、前の値に応じて分岐する機能です。通常の>>=機能は、この分岐機能への最も一般的に有用なインターフェースとして選択されました。

例として、Maybeモナドは、ある段階で失敗する可能性のあるプログラムを提供します(コンテキストは失敗状態です)。この疑似Haskellの例を考えてみましょう:

-- take a computation that produces an Int.  If the current Int is even, add 1.
incrIfEven :: Monad m => m Int -> m Int
incrIfEven anInt =
    let ourInt = currentStateOf anInt
    in if even ourInt then return (ourInt+1) else return ourInt

計算の現在の結果に基づいて分岐するには、その現在の結果にアクセスできる必要があります。上記の疑似コードは、にアクセスできれば機能しますがcurrentStateOf :: m a -> a、モナドでは通常は不可能です。代わりに、タイプの関数として分岐するという決定を記述しますa -> m b。この関数ではモナドに含まれていないためa、通常の値のように扱うことができ、操作がはるかに簡単になります。

incrIfEvenReal :: Monad m => m Int -> m Int
incrIfEvenReal anInt = anInt >>= branch
  where branch ourInt = if even ourInt then return (ourInt+1) else return ourInt

したがって、のタイプ>>=は実際にはプログラミングを容易にするためのものですが、場合によってはより便利ないくつかの選択肢があります。特に、関数Control.Monad.joinと組み合わせると、とfmapまったく同じパワーが得られます>>=(どちらか一方を他方で定義できます)。

于 2012-08-15T15:07:43.523 に答える
0

関数は、必要に応じてモナディック値を取ることができます。しかし、そうすることを強制されていません。

Data.Charのリストモナドと関数を使用して、次の不自然な定義を検討してください。

m :: [[Int]]
m = [[71,72,73], [107,106,105,104]]

f :: [Int] -> [Char]
f mx = do
    g <- [toUpper, id, toLower]
    x <- mx
    return (g $ chr x)

あなたは確かに実行することができますm >>= f; 結果のタイプは[Char]

(ここで重要なのは、最初の引数から常に1つのモナディックレイヤーを「取り除く」ことではm :: [[Int]]ありませんm :: [Int]>>=それを望まない場合は、f m代わりに行ってm >>= fください。)

于 2012-08-15T10:56:43.103 に答える
0

他の人が述べているように、そのような関数の記述を制限するものは何もありません。

実際、次のタイプの関数の大きなファミリーがあります:: m a -> (m a -> m b) -> m b

f :: Monad m => Int -> m a -> (m a -> m b) -> m b
f n m mf = replicateM_ n m >>= mf m

どこ

f 0 m mf = mf m

f 1 m mf = m >> mf m

f 2 m mf = m >> m >> mf m

...など..。

(基本ケースに注意してください。nが0の場合、それは単に通常の機能アプリケーションです。)

しかし、この関数は何をしますか?モナディックアクションを複数回実行し、最終的にすべての結果を破棄して、mfのアプリケーションをmに返します。

時々便利ですが、特に。と比較すると、一般的にはほとんど役に立ちません>>=

Hoogleをすばやく検索しても、結果は表示されません。おそらくわかりやすい結果です。

于 2012-08-15T15:08:48.260 に答える