10

先日、関数型プログラミングについて話していました。特に、Java / Scalaの人たちとHaskellについて話していたところ、モナドとは何か、どこに必要なのかと聞かれました。

まあ、定義と例はそれほど難しいものではありませんでした- Maybe Monad、など、それでIO MonadState Monadモナドは良いことだと言って、少なくとも部分的には、誰もが私に大丈夫でした。

しかし、モナドが必要な場所はどこですか?の設定やの設定のMaybeような魔法の値を介して回避できます。私はモナドなしでゲームを書きましたが、それはまったくいいことではありませんが、初心者はそうします。-1Integer""StringState

だから私の質問:モナドはどこに必要ですか?-そして、まったく避けられません。(そして混乱はありません-私はモナドが好きで、それらを使用しています、私は知りたいだけです)。

編集

「マジック値」を使用することは良い解決策ではないと思うことを明確にする必要があると思いますが、多くのプログラマーは、特にCのような低水準言語や、を返すことによってエラーが暗示されることが多いシェルスクリプトでそれらを使用します-1

モナドを使用しないのは良い考えではないことはすでに私には明らかでした。抽象化はしばしば非常に役立ちますが、取得するのも複雑であるため、多くの人がモナドの概念に苦労しています。

私の質問の核心は、たとえばIO、モナドがなくても、純粋で機能的であることが可能かどうかということでした。ライターを使用する代わりに、火打ち石と火打ち石で火を灯すだけでなく、既知の良い解決策を脇に置くのは退屈で苦痛だと私は知っていました。

@Antal SZが言及している記事は、モナドを発明できたのは素晴らしいことです。私はそれをざっと読みました。時間があれば間違いなく読みます。より明白な答えは、@ Antal SZによって参照されたブログ投稿のコメントに隠されています。私が質問したときに探していたのは、モナドの前の時間を覚えています。

4

3 に答える 3

12

モナドは必要ないと思います。これらは、特定の種類の関数を操作しているときに自然に表示されるパターンにすぎません。私が今まで見たこの観点の最も良い説明は、ダン・ピポーニ(sigfpe)の優れたブログ投稿「あなたはモナドを発明したかもしれない!(そして多分あなたはすでに持っている)」であり、この答えはこの答えに触発されています。

あなたは州のモナドを使わずにゲームを書いたと言います。それはどのように見えましたか?次のようなタイプの関数を使用することになった可能性がありますopenChest :: Player -> Location -> (Item,Player)(胸を開き、トラップでプレーヤーに損害を与え、見つかったアイテムを返す可能性があります)。これらを組み合わせる必要がある場合は、手動で行う(let (item,player') = openChest player loc ; (x,player'') = func2 player' y in ...)か、状態モナドの>>=演算子を再実装することができます。

または、ハッシュマップ/連想配列を使用する言語で作業していて、モナドを使用していないとします。いくつかの項目を調べて、それらを操作する必要があります。2人のユーザー間でメッセージを送信しようとしている可能性があります。

send username1 username2 = {
    user1 = users[username1]
    user2 = users[username2]
    sendMessage user1 user2 messageBody
}

しかし、待ってください、これは機能しません。欠落している可能性がありusername1ます。その場合、目的の値ではなく、または何かになります。または、連想配列でキーを検索すると、型の値が返されるため、これは型エラーになることもあります。代わりに、次のようなものを書く必要がありますusername2nil-1Maybe a

send username1 username2 = {
    user1 = users[username1]
    if (user1 == nil) return
    user2 = users[username2]
    if (user2 == nil) return
    sendMessage user1 user2 messageBody
}

または、を使用してMaybe

send username1 username2 =
    case users[username1] of
      Just user1 -> case users[username2] of
                      Just user2 -> Just $ sendMessage user1 user2 messageBody
                      Nothing    -> Nothing
      Nothing    -> Nothing

いや!これは厄介で、過度にネストされています。そこで、失敗する可能性のあるアクションを組み合わせたある種の関数を定義します。多分何かのような

(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
f >>= Just x  = f x
f >>= Nothing = Nothing

だからあなたは書くことができます

send username1 username2 =
  users[username1] >>= $ \user1 ->
  users[username2] >>= $ \user2 ->
  Just (sendMessage user1 user2 messageBody)

本当に使いたくない場合はMaybe、実装することができます

f >>= x = if x == nil then nil else f x

同じ原則が適用されます。

しかし、実際には、「モナドを発明できたかもしれない」を読むことをお勧めします。 それは私がモナドについてこの直感を得た場所であり、それをより良くそしてより詳細に説明します。モナドは、特定のタイプで作業するときに自然に発生します。その構造を明示的にすることもあれば、そうでないこともありますが、それを控えているからといって、そこにないというわけではありません。その特定の構造で作業する必要がないという意味でモナドを使用する必要はありませんが、多くの場合、それは自然なことです。そして、ここでは他の多くのものと同様に、一般的なパターンを認識することで、いくつかのうまく一般的なコードを書くことができます。

Maybe(また、私が使用した2番目の例が示すように、魔法の値に置き換えて、お風呂の水で赤ちゃんを捨てたことに注意してください。モナドであるからMaybeといって、モナドのように使用する必要があるわけではありません。リストもモナドです。 、(形式のr ->)関数もそうですが、それらを取り除くことを提案することはありません!:-))

于 2012-08-23T18:27:11.653 に答える
4

「 Xはどこで必要であり、避けられないのか」というフレーズをとることができます。ここで、Xはコンピューティングにおけるあらゆるものです。ポイントは何でしょうか?

代わりに、「 Xはどのような価値を提供しますか?」 と尋ねる方が価値があると思います。

そして、最も基本的な答えは、コンピューティングにおけるほとんどのXは、コードをまとめるのをより簡単に、面倒でなく、エラーが発生しにくいようにする有用な抽象化を提供するということです。

わかりましたが、抽象化は必要ありませんよね?つまり、同じことを行う小さなコードを手で入力するだけでいいのですよね?ええ、もちろん、それはすべて0と1の集まりなので、Java / Haskell / Cを使用している私、またはチューリングマシンを使用しているあなたのどちらがXMLパーサーをより速く記述できるか見てみましょう。


モナドについて:モナドは通常、効果的な計算を処理するため、この抽象化は、効果的な関数を作成するときに最も役立ちます。

私はあなたの「魔法の価値多分モナド」に問題を抱えています。このアプローチは、プログラマーに非常に異なる抽象化を提供し、実際の Maybeモナドよりも安全性が低く、面倒で、エラーが発生しやすくなります。また、そのようなコードを読むと、プログラマーの意図はあまり明確ではなくなります。言い換えれば、それは抽象化を提供することである実際のモナドの全体のポイントを見逃しています。

また、モナドはHaskellの基本ではないことにも注意したいと思います。

  1. do-表記は単なる構文糖衣であり、表現力を失うことなく完全>>=に置き換えることができます>>

  2. それら(および、、、などのそれらのコンビネータ)joinHaskellで書くことができます>>=mapM

  3. これらは、高階関数をサポートする任意の言語で記述できます。また、オブジェクトを使用してJavaで記述することもできます。したがって、モナドを持たないLispで作業する必要がある場合は、あまり問題なくそのLispにモナドを実装できます。

于 2012-08-23T18:29:34.320 に答える
0

モナド型は同じ型の回答を返すため、そのモナド型の実装はセマンティクスを強制および保持できます。次に、コードで、そのタイプで操作を連鎖させ、含まれているタイプに関係なく、ルールを適用させることができます。

たとえば、Java 8のOptionalクラスは、含まれている値が存在するかnullでないか、または存在しないというルールを適用します。flatMapを使用するかどうかに関係なく、Optionalクラスを使用している限り、含まれているデータ型の周りにそのルールをラップします。だれもごまかしたり忘れたりして、present=trueでvalue=nullを追加することはできません。

したがって、コードの外部で-1が番兵の値であり、そのようなものを意味することを宣言することは問題ありませんが、それでも、そのセマンティクスを尊重するために、コードで作業している自分自身と他の人々に依存しています。新しい人が加わり、同じことを意味するために-1000000を使い始めた場合、セマンティクスは、コードメカニズムではなく、コードの外部で(おそらくリードパイプを使用して)強制する必要があります。

したがって、プログラムに一貫して何らかのルールを適用する必要はなく、モナドを信頼して、任意のタイプに対してそのルール(または他のセマンティクス)を保持することができます。

このようにして、コードベースのすべての型に「isPresent」を追加する代わりに、型のセマンティクスをラップすることで型の機能を拡張できます。

多数のモナディックユーティリティタイプの存在は、タイプをセマンティクスでラップするこのメカニズムが非常に便利なトリックであるという事実を示しています。追加したい独自のセマンティクスがある場合は、モナドパターンを使用して独自のクラスを記述し、それに文字列、float、intなどを挿入することでそれを行うことができます。

しかし、簡単に言えば、モナドは、基本的な型の実装に煩わされることなく、一般的な型を流暢なまたはチェーン可能なコンテナーにラップして、ルールと使用法を追加するための優れた方法です。

于 2015-11-18T23:23:51.340 に答える