web-dev-qa-db-ja.com

多分モナドvs例外

例外に対するMaybemonad の利点は何ですか? Maybetry..catch構文の明示的な(そしてスペースを消費する)方法のようです。

updateHaskellについては意図的にではないことに注意してください。

34
Vladimir

Maybe(またはその従兄弟のEitherは基本的に同じように機能しますが、Nothingの代わりに任意の値を返すことができます)を使用すると、例外とは少し目的が異なります。 Java用語では、実行時の例外ではなく、チェックされた例外があるようなものです。これは、何かを表しますexpected対処しなければならないエラーではなく、対処する必要があります期待する。

そのため、indexOfのような関数は、Maybe値を返します。これは、アイテムがリストにない可能性があるためです。これは、nullの場合に対処する必要があるタイプセーフな方法を除いて、関数からnullを返すのとよく似ています。 Eitherは同じ方法で機能しますが、エラーケースに関連する情報を返すことができるため、実際にはMaybeよりも例外に似ています。

では、Maybe/Eitherアプローチの利点は何ですか?一つには、それは言語のファーストクラスの市民です。 Eitherを使用する関数を、例外をスローする関数と比較してみましょう。例外的なケースでは、あなたの唯一の本当の手段はtry...catchステートメントです。 Either関数の場合、既存のコンビネーターを使用してフロー制御をより明確にすることができます。次に例をいくつか示します。

最初に、エラーにならない関数を取得するまで、連続してエラーになる可能性のあるいくつかの関数を試したいとしましょう。エラーが発生しない場合は、特別なエラーメッセージを返します。これは実際には非常に便利なパターンですが、try...catchを使用すると恐ろしい痛みになります。幸い、Eitherは通常の値なので、既存の関数を使用してコードをより明確にすることができます。

firstThing <|> secondThing <|> throwError (SomeError "error message")

別の例としては、オプション機能があります。クエリを最適化しようとする機能など、実行する機能がいくつかあるとします。これが失敗した場合は、他のすべてをとにかく実行する必要があります。次のようなコードを書くことができます:

do a <- getA
   b <- getB
   optional (optimize query)
   execute query a b

これらのケースはどちらもtry..catchを使用するよりも明確で短く、さらに重要なことに、セマンティックです。 <|>optionalなどの関数を使用すると、try...catchを使用して常に例外を処理するよりもmuchの意図が明確になります。

また、しないでくださいは、if a == Nothing then Nothing else ...MaybeEitherをモナドとして扱うことの要点はこれを避けることです。伝搬セマンティクスをbind関数にエンコードして、null /エラーチェックを無料で取得できます。明示的にチェックする必要があるのは、Nothingを指定してNothing以外のものを返したい場合だけです。それでも簡単です。そのコードをより美しくするための標準ライブラリ関数がたくさんあります。

最後に、もう1つの利点は、Maybe/Either型の方が単純であることです。追加のキーワードや制御構造で言語を拡張する必要はありません。すべてが単なるライブラリです。それらは単なる通常の値なので、型システムをより単純にします-Javaでは、throwsを使用しない場合の型(戻り値の型など)と効果(Maybeステートメントなど)を区別する必要があります。また、他のユーザー定義型と同じように動作します。言語に特別なエラー処理コードを組み込む必要はありません。

もう1つの勝利は、Maybe/Eitherがファンクターとモナドであることです。つまり、既存のモナド制御フロー関数(かなりの数があります)を利用でき、一般に、他のモナドとうまく連動します。

とはいえ、いくつかの注意点があります。まず、MaybeEithernchecked例外を置き換えません。 0で除算するなど、別の方法で処理する必要があります。これは、すべての除算でMaybeの値を返すのは面倒だからです。

別の問題は、複数のタイプのエラーが返されることです(これはEitherにのみ適用されます)。例外を使用すると、同じ関数でさまざまなタイプの例外をスローできます。 Eitherを使用すると、1つのタイプしか取得できません。これは、サブタイピングまたはコンストラクターとしてすべての異なるタイプのエラーを含むADTで克服できます(この2番目のアプローチは、Haskellで通常使用されるものです)。

それでも、全体的に、Maybe/Eitherのアプローチの方が簡単で柔軟性があるため、私はそれを好みます。

57
Tikhon Jelvis
  1. 例外は、問題の原因に関するより多くの情報を伝えることができます。 OpenFile()FileNotFoundまたはNoPermissionまたはTooManyDescriptorsなどをスローできます。Aにはこの情報は含まれません。
  2. 例外は、戻り値がないコンテキストで使用できます(例:戻り値を持つ言語のコンストラクター)。
  3. 例外を使用すると、多くのif None return Noneスタイルのステートメントを使用しなくても、非常に簡単にスタックに情報を送信できます。
  4. 例外処理は、ほとんどの場合、単に値を返すよりもパフォーマンスに大きな影響を与えます。
  5. 最も重要なのは、例外とMaybeモナドの目的が異なることです。例外は問題を示すために使用されますが、Maybeはそうではありません。

    「看護師、if 5部屋に患者がいます。待つように頼むことができますか?」

    • たぶんモナド:「ドクター、部屋5には患者がいません」
    • 例外:「ドクター、5部屋はありません!」

    (「if」に注意してください-これは、医師が期待している多分モナドであることを意味します)

11
Oak

私はTikhonの答えに2番目ですが、誰もが欠けている非常に重要な実用的なポイントがあると思います。

  1. 少なくとも主流の言語の例外処理メカニズムは、個々のスレッドと密接に結びついています。
  2. Eitherメカニズムは、スレッドにまったく結合されていません。

ですから、今日私たちが実際に目にしているのは、多くの非同期プログラミングソリューションがEitherスタイルのエラー処理のバリアントを採用しているということです。これらのリンクのいずれかに詳述されているように、Javascript約束を検討してください:

Promiseの概念では、次のような非同期コードを記述できます(最後のリンクから取得)。

var greetingPromise = sayHello();
greetingPromise
    .then(addExclamation)
    .then(function (greeting) {
        console.log(greeting);    // 'hello world!!!!’
    }, function(error) {
        console.error('uh oh: ', error);   // 'uh oh: something bad happened’
    });

基本的に、promiseは次のようなオブジェクトです。

  1. 非同期計算の結果を表します。まだ完了していない場合もあります。
  2. その結果に対して実行する追加の操作をチェーンして、その結果が利用可能になるとトリガーされ、その結果がプロミスとして利用可能になります。
  3. Promiseの計算が失敗した場合に呼び出される失敗ハンドラーをフックできます。ハンドラーがない場合、エラーはチェーン内の後のハンドラーに伝搬されます。

基本的に、計算が複数のスレッドで発生している場合、言語のネイティブ例外サポートは機能しないため、promise実装はエラー処理メカニズムを提供する必要があり、これらはHaskellのMaybe /と同様のモナドであることが判明します。 Eitherタイプ。

6
sacundim

「たぶん」は例外の代わりではありません。例外は、例外的なケースで使用することを目的としています(たとえば、db接続を開いているときに、dbサーバーがそこにないはずです)。 「たぶん」は、有効な値がある場合とない場合の状況をモデル化するためのものです。キーのディクショナリから値を取得しているとしましょう。それはある場合もない場合もあります。これらの結果のいずれにも「例外」はありません。

6

Haskell型システムでは、ユーザーがNothingの可能性を認識する必要がありますが、プログラミング言語では、例外をキャッチする必要がないことがよくあります。つまり、コンパイル時に、ユーザーがエラーをチェックしたことがわかります。

1
chrisaycock

例外処理は、因数分解とテストにとって非常に困難な場合があります。私は知っていますpythonは、厳密な "try ... catch"ブロックなしで例外をトラップできる素敵な "with"構文を提供します。しかし、たとえばJavaでは、try catchブロックは大きく、定型的です、冗長または非常に冗長で、分割するのは難しいです。さらに、Javaは、チェックされた例外とチェックされていない例外の周囲にすべてのノイズを追加します。

代わりに、モナドが例外をキャッチし、それらをモナド空間のプロパティとして処理する場合(処理の異常ではなく)、スローまたはキャッチするものに関係なく、そのスペースにバインドする関数を自由に組み合わせて使用​​できます。

さらに良いことに、モナドが例外が発生する可能性のある状態(NullチェックをMaybeにプッシュするなど)を防ぐ場合は、さらに良いでしょう。もし...それなら、試して...キャッチするよりもはるかに簡単に因数分解してテストすることができます。

Goは、各関数が(answer、error)を返すように指定することで、同様のアプローチをとっています。これは、関数をモナド空間に「持ち上げる」のと同じようなもので、コアの回答タイプにエラーが表示され、例外のスローとキャッチが効果的に行われます。

0
Rob

多分モナドは基本的にほとんどの主流言語の「nullはエラーをチェックする」チェックと同じです(ただし、nullをチェックする必要がある点を除きます)。主に同じ利点と欠点があります。

0
Telastyn