回答:
使用Maybe
(またはそのいとこEither
基本的には同じように動作しますが、あなたの代わりに任意の値を返すことができますNothing
)例外よりもわずかに異なる目的を果たします。Javaの用語では、実行時例外ではなくチェック例外を持っているようなものです。これは、予期していなかったエラーではなく、対処しなければならない予想される何かを表します。
そのため、次のような関数indexOf
はMaybe
値が返されます。これは、アイテムがリストに含まれていない可能性があるためです。これはnull
、null
ケースからの対処を強制するタイプセーフな方法を除いて、関数から戻ることによく似ています。Either
エラーケースに関連付けられた情報を返すことができることを除いて、同じように機能します。したがって、実際には例外よりも例外に似ていますMaybe
。
Maybe
/ Either
アプローチの利点は何ですか?1つは、言語の第一級市民です。を使用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
。
また、!のような行でコードを散らかす必要がないことに注意してくださいif a == Nothing then Nothing else ...
。治療の全体のポイントMaybe
とEither
モナドとしては、これを避けるためです。伝播セマンティクスをバインド関数にエンコードして、null /エラーチェックを無料で取得できます。明示的に確認する必要があるのはNothing
、a 以外の何かを返したい場合だけです。Nothing
それでも簡単です。コードをより良くするための標準ライブラリ関数がたくさんあります。
最後に、もう1つの利点は、Maybe
/ Either
タイプが単純であることです。追加のキーワードや制御構造を使用して言語を拡張する必要はありません。すべてが単なるライブラリです。これらは単なる通常の値であるため、型システムを単純にします。Javaでは、使用しない型(戻り型など)と効果(throws
ステートメントなど)を区別する必要がありますMaybe
。また、他のユーザー定義型と同様に動作します。特別なエラー処理コードを言語に組み込む必要はありません。
もう一つの勝利は、あるMaybe
/ Either
彼らは(公正な数があるの)既存のモナドの制御フロー機能を利用すると、一般的には、他のモナドと一緒にきれいに再生することができることを意味、ファンクタとモナドです。
ただし、いくつかの注意事項があります。1つは、チェックされていない例外を置き換えMaybe
たり、Either
置き換えたりしないことです。すべての部門がMaybe
値を返すのは苦痛になるため、0で割るなどのことを処理する他の方法が必要になります。
別の問題は、複数のタイプのエラーが返されることです(これはにのみ適用されますEither
)。例外を使用すると、同じ関数でさまざまなタイプの例外をスローできます。ではEither
、1つのタイプしか取得できません。これは、サブタイピングまたはコンストラクターとしてすべての異なるタイプのエラーを含むADTで克服できます(この2番目のアプローチは、Haskellで通常使用されます)。
それでも、全体として、よりシンプルで柔軟性があると思うので、Maybe
/ Either
アプローチを好みます。
OpenFile()
投げることができるFileNotFound
か、NoPermission
またはTooManyDescriptors
などAなしは、この情報を運ぶません。if None return None
-styleステートメントを使用せずに、非常に簡単に情報をスタックに送信できます。最も重要なことは、例外とMaybeモナドの目的が異なることです。例外は問題を示すために使用されますが、Maybeはそうではありません。
「ナース、場合部屋5で患者があります、あなたは待つように彼を求めることができますか?」
(「if」に注意してください-これは、医師がMaybeモナドを期待していることを意味します)
bind
、テストでNone
構文上のオーバーヘッドが発生しないように記述できます。非常に簡単な例として、C#はNullable
演算子を適切にオーバーロードします。None
タイプを使用する場合でも、必要なチェックはありません。もちろん、チェックはまだ行われます(タイプセーフです)が、舞台裏でコードが乱雑になることはありません。(5)に対する私の異議に対するあなたの異議にも何らかの意味で同じことが当てはまりますが、常に適用されるとは限らないことに同意します。
Maybe
、モナドとして扱うことの全体的なポイントは、伝播をNone
暗黙的にすることです。つまり、None
given を返したい場合None
、特別なコードを書く必要はまったくありません。あなたがマッチする必要がある唯一の時間はあなたが特別なことをしたいかどうかですNone
。if None then None
一種のステートメントは必要ありません。
null
正確にそのようなチェックをするということです(例えばif Nothing then Nothing
)。のbind()の定義でエンコードされています。Maybe
>>=
Maybe
Either
)のように振る舞いMaybe
ます。2つの切り替えは実際にかなり簡単Maybe
ですEither
。(でHaskellは、あなたが考える可能性Maybe
などEither ()
。)
「たぶん」は例外の代わりではありません。例外は、例外的な場合に使用することを目的としています(たとえば、db接続を開くと、dbサーバーが存在するはずですが、存在しません)。「たぶん」は、有効な値がある場合とない場合がある状況をモデル化するためのものです。キーのディクショナリから値を取得していると言います。キーが存在する場合と存在しない場合があります。これらの結果のいずれにも「例外」はありません。
Tikhonの答えは2番目ですが、誰もが欠けている非常に重要な実用的なポイントがあると思います。
Either
メカニズムは、スレッドにまったく結合されていません。現実の世界で私たちが目にしているのは、多くの非同期プログラミングソリューションが- Either
スタイルのエラー処理のバリアントを採用していることです。これらのリンクのいずれかで詳述されているように、Javascript promise を考慮してください。
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は以下のオブジェクトです:
基本的に、複数のスレッド間で計算が行われている場合、言語のネイティブ例外サポートは機能しないため、Promiseの実装ではエラー処理メカニズムを提供する必要があり、これらはHaskellのMaybe
/ Either
型に類似したモナドになります。
Haskell型システムでは、ユーザーがaの可能性を確認する必要がありますがNothing
、プログラミング言語では多くの場合、例外をキャッチする必要はありません。つまり、コンパイル時に、ユーザーがエラーをチェックしたことがわかります。
throws NPE
、すべての署名とcatch(...) {throw ...}
すべてのメソッド本体にを追加する必要があるという意味でチェックされることを望まないでしょう。しかし、Maybeと同じ意味でチェック対象の市場があると考えています。null可能性はオプションであり、型システムで追跡されます。
多分モナドは基本的にほとんどの主流言語の「nullはエラーを意味する」チェックの使用と同じであり(nullをチェックする必要があることを除く)、ほぼ同じ長所と短所があります。
Maybe
書くだけで2つの数値を追加することができ、結果は再びオプションの値になります。a + b
None
Maybe
typeにも当てはまりますがMaybe
、モナドとして使用すると、nullのようなロジックをよりエレガントに表現できる構文シュガーが追加されます。
例外処理は、ファクタリングとテストの大きな痛みになる可能性があります。pythonには、厳格な「try ... catch」ブロックなしで例外をトラップできる「with」構文が用意されています。しかし、たとえばJavaでは、try catchブロックは大きく、定型的で、冗長または極度に冗長であり、分割が困難です。それに加えて、Javaはチェック済み例外と未チェック例外の周囲にすべてのノイズを追加します。
代わりに、モナドが例外をキャッチし、(何らかの処理異常ではなく)モナド空間のプロパティとして処理する場合、スローまたはキャッチするものに関係なく、その空間にバインドする関数を自由に組み合わせて一致させることができます。
さらに良いことに、モナドが例外が発生する可能性のある条件(NullチェックをMaybeにプッシュするなど)を防ぐ場合は、さらに良いでしょう。if ... thenは、try ... catchよりも、ファクタリングとテストがはるかに簡単です。
私が見てきたことから、Goは各関数が返す(答え、エラー)ことを指定することにより、同様のアプローチを取っています。これは、関数をモナド空間に「リフティング」するのと同じようなもので、コア応答タイプにはエラー表示があり、例外をスローしてキャッチするのを効果的に回避します。