たぶんモナドと例外


回答:


57

使用Maybe(またはそのいとこEither基本的には同じように動作しますが、あなたの代わりに任意の値を返すことができますNothing)例外よりもわずかに異なる目的を果たします。Javaの用語では、実行時例外ではなくチェック例外を持っているようなものです。これは、予期していなかったエラーではなく、対処しなければならない予想される何かを表します。

そのため、次のような関数indexOfMaybe値が返されます。これは、アイテムがリストに含まれていない可能性があるためです。これはnullnullケースからの対処を強制するタイプセーフな方法を除いて、関数から戻ることによく似ています。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 ...。治療の全体のポイントMaybeEitherモナドとしては、これを避けるためです。伝播セマンティクスをバインド関数にエンコードして、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アプローチを好みます。


ほとんど同意しましたが、「オプション」の例は理にかなっていないと思います-例外を除いて同様にそれを行うことができるようです:void Optional(Action act){try {act(); } catch(Exception){}}
Errorsatz

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

    「ナース、場合部屋5で患者があります、あなたは待つように彼を求めることができますか?」

    • おそらくモナド:「医師、部屋5に患者はいない」
    • 例外:「博士、部屋5はありません!」

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


7
ポイント2と3には同意しません。特に、モナドを使用する言語では、構築中に障害を処理しなければならないという難問を生み出さない慣習があるため、実際には2は問題になりません。3に関して、モナドを含む言語に、明示的なチェックを必要としない透過的な方法でモナドを処理するための適切な構文があります(たとえば、None値を伝播するだけです)。あなたのポイント5は、一種の正しいだけです…質問は次のとおりです。どの状況が明白に例外的であるか?結局のところ… 多くはありません
コンラッドルドルフ

3
(2)についてはbind、テストでNone構文上のオーバーヘッドが発生しないように記述できます。非常に簡単な例として、C#はNullable演算子を適切にオーバーロードします。Noneタイプを使用する場合でも、必要なチェックはありません。もちろん、チェックはまだ行われます(タイプセーフです)が、舞台裏でコードが乱雑になることはありません。(5)に対する私の異議に対するあなたの異議にも何らかの意味で同じことが当てはまりますが、常に適用されるとは限らないことに同意します。
コンラッドルドルフ

4
@Oak:(3)に関してMaybe、モナドとして扱うことの全体的なポイントは、伝播をNone暗黙的にすることです。つまり、Nonegiven を返したい場合None、特別なコードを書く必要はまったくありません。あなたがマッチする必要がある唯一の時間はあなたが特別なことをしたいかどうかですNoneif None then None一種のステートメントは必要ありません。
Tikhon Jelvis

5
@オーク:それは本当ですが、それは本当に私のポイントではありませんでした。私が言っているのは、モナドだから無料でnull正確にそのようなチェックをするということです(例えばif Nothing then Nothing)。のbind()の定義でエンコードされています。Maybe>>=Maybe
ティコンジェルビス

2
また、(1)に関して:エラー情報を運ぶことができるモナドを簡単に書くことができます(例Either)のように振る舞いMaybeます。2つの切り替えは実際にかなり簡単MaybeですEither。(でHaskellは、あなたが考える可能性MaybeなどEither ()。)
Tikhon Jelvis

6

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


2
実際、キーが見つからないということは、ある時点で何かが恐ろしく間違っていることを意味する辞書を含むかなりのコードがあります。おそらく、私のコードまたはその時点で使用されるものに入力を変換するコードのバグです。

3
@delnan:欠損値が例外的な状態の兆候になることはない、と言っているわけではありません-必ずしもそうである必要はありません。変数が有効な値を持つ場合と持たない場合の状況を実際にモデル化するのかもしれません-C#のnull許容型を考えてください。
ネマンジャトリフノヴィッチ

6

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

  1. 少なくとも主流言語の例外処理メカニズムは、個々のスレッドに密接に結合されています。
  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は以下のオブジェクトです:

  1. 非同期計算の結果を表します。まだ完了していない場合もあります。
  2. 結果を実行するためにさらに操作を連鎖できます。結果は、その結果が使用可能になるとトリガーされ、その結果は約束として使用可能になります。
  3. Promiseの計算が失敗した場合に呼び出される失敗ハンドラーを接続できます。ハンドラーがない場合、エラーはチェーン内の後のハンドラーに伝搬されます。

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


スレッドとは何の関係もありません。ブラウザのJavaScriptは、複数のスレッドではなく、常に1つのスレッドで実行されます。ただし、将来関数がいつ呼び出されるかわからないため、例外を使用することはできません。非同期は、スレッドの関与を自動的に意味するものではありません。それが例外を扱うことができない理由でもあります。関数を呼び出してすぐに実行される場合にのみ、例外をフェッチできます。しかし、非同期の全体的な目的は、多くの場合、すぐにではなく、他の何かが終了したときに、それが将来実行されることです。そのため、そこで例外を使用することはできません。
デビッドラブ

1

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


1
Javaにはチェック例外があります。そして人々はしばしば彼らでさえもひどく扱いました。
ウラジミール

1
@ DairT'arg ButJavaではIOエラーのチェックが必要ですが(例として)、NPEのチェックは必要ありません。Haskellでは、これとは逆の方法です。null相当(多分)がチェックされますが、IOエラーは単に(チェックされていない)例外をスローします。どれほどの違いが生じるかはわかりませんが、Haskellersが多分不満を言うのは聞いていませんし、多くの人がNPEをチェックすることを望んでいると思います。

1
@delnan人々はNPEをチェックしたいですか?彼らは怒っている必要があります。そして、私はそれを意味します。NPEをチェックすることは、そもそもそれを回避する方法がある場合にのみ意味があります(Javaにはないnull不可の参照を使用することによって)。
コンラッドルドルフ

5
@KonradRudolphはい、人々はおそらくthrows NPE、すべての署名とcatch(...) {throw ...} すべてのメソッド本体にを追加する必要があるという意味でチェックされることを望まないでしょう。しかし、Maybeと同じ意味でチェック対象の市場があると考えています。null可能性はオプションであり、型システムで追跡されます。

1
@delnanああ、それから同意します。
コンラッドルドルフ

0

多分モナドは基本的にほとんどの主流言語の「nullはエラーを意味する」チェックの使用と同じであり(nullをチェックする必要があることを除く)、ほぼ同じ長所と短所があります。


8
正しく使用すると静的に型チェックできるため、同じ欠点はありません。多分モナドを使用する場合、nullポインター例外に相当するものはありません(再び、それらが正しく使用されると仮定します)。
コンラッドルドルフ

確かに。それをどのように明確にすべきだと思いますか?
テラスティン

@Konradこれは、おそらく(おそらく)値を使用するために、Noneをチェックする必要があるためです(もちろん、多くのチェックを自動化する機能があります)。他の言語は、そのようなことを手動で保持します(主に私が信じる哲学的な理由のため)。
ドナルドフェローズ

2
@Donalはい。ただし、モナドを実装するほとんどの言語は適切な構文糖を提供するため、このチェックは完全に隠されることがよくあります。たとえば、をチェックせずMaybe書くだけで2つの数値を追加することができ、結果は再びオプションの値になります。a + b None
コンラッドルドルフ

2
nullable型との比較はMaybe typeにも当てはまりますがMaybe、モナドとして使用すると、nullのようなロジックをよりエレガントに表現できる構文シュガーが追加されます。
-tdammers

0

例外処理は、ファクタリングとテストの大きな痛みになる可能性があります。pythonには、厳格な「try ... catch」ブロックなしで例外をトラップできる「with」構文が用意されています。しかし、たとえばJavaでは、try catchブロックは大きく、定型的で、冗長または極度に冗長であり、分割が困難です。それに加えて、Javaはチェック済み例外と未チェック例外の周囲にすべてのノイズを追加します。

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

さらに良いことに、モナドが例外が発生する可能性のある条件(NullチェックをMaybeにプッシュするなど)を防ぐ場合は、さらに良いでしょう。if ... thenは、try ... catchよりも、ファクタリングとテストがはるかに簡単です。

私が見てきたことから、Goは各関数が返す(答え、エラー)ことを指定することにより、同様のアプローチを取っています。これは、関数をモナド空間に「リフティング」するのと同じようなもので、コア応答タイプにはエラー表示があり、例外をスローしてキャッチするのを効果的に回避します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.