例外をキャッチ/スローすると、純粋なメソッドが不純になりますか?


27

次のコード例は、私の質問の背景を示しています。

Roomクラスはデリゲートで初期化されます。Roomクラスの最初の実装では、例外をスローするデリゲートに対するガードはありません。このような例外は、デリゲートが評価されるNorthプロパティにバブルアップします(注:Main()メソッドは、クライアントコードでRoomインスタンスがどのように使用されるかを示します)。

public sealed class Room
{
    private readonly Func<Room> north;

    public Room(Func<Room> north)
    {
        this.north = north;
    }

    public Room North
    {
        get
        {
            return this.north();
        }
    }

    public static void Main(string[] args)
    {
        Func<Room> evilDelegate = () => { throw new Exception(); };

        var kitchen = new Room(north: evilDelegate);

        var room = kitchen.North; //<----this will throw

    }
}

Northプロパティを読み取るときではなく、オブジェクトの作成時に失敗するので、コンストラクターをprivateに変更し、Create()という名前の静的ファクトリーメソッドを導入します。このメソッドは、デリゲートによってスローされた例外をキャッチし、意味のある例外メッセージを持つラッパー例外をスローします。

public sealed class Room
{
    private readonly Func<Room> north;

    private Room(Func<Room> north)
    {
        this.north = north;
    }

    public Room North
    {
        get
        {
            return this.north();
        }
    }

    public static Room Create(Func<Room> north)
    {
        try
        {
            north?.Invoke();
        }
        catch (Exception e)
        {
            throw new Exception(
              message: "Initialized with an evil delegate!", innerException: e);
        }

        return new Room(north);
    }

    public static void Main(string[] args)
    {
        Func<Room> evilDelegate = () => { throw new Exception(); };

        var kitchen = Room.Create(north: evilDelegate); //<----this will throw

        var room = kitchen.North;
    }
}

try-catchブロックはCreate()メソッドを不純にしますか?


1
ルーム作成機能の利点は何ですか?デリゲートを使用している場合、クライアントが「North」を呼び出す前にデリゲートを呼び出すのはなぜですか?
-SH-

1
@SHデリゲートが例外をスローする場合、Roomオブジェクトの作成時に調べたいです。クライアントが使用時に例外を見つけるのではなく、作成時に例外を見つけたいです。Createメソッドは、邪悪なデリゲートを公開するのに最適な場所です。
ロックアンソニージョンソン

3
@RockAnthonyJohnson、はい。デリゲートを実行すると、最初にしか機能しない場合や、2回目の呼び出しで別の部屋に戻る場合はどうしますか?デリゲートを呼び出すことは考慮されることができます/副作用自体を引き起こしますか?
SH-

4
不純な関数を呼び出す関数は不純な関数であるため、デリゲートが不純な場合はCreateそれを呼び出すため、不純な関数でもあります。
イダンアリー

2
あなたのCreate関数は、プロパティを取得するときに例外を得ることからあなたを保護することはできません。デリゲートがスローされる場合、実際には、特定の条件下でのみスローされる可能性が非常に高くなります。建設中に投げるための条件が存在しない可能性がありますが、プロパティを取得するときに存在します。
バートヴァンインゲンシェナウ

回答:


26

はい。 これは事実上不純な機能です。副作用が発生します。プログラムの実行は、関数が戻ると予想される場所以外の場所で継続します。

それを純粋な関数にするために、関数returnから期待される値と、Maybeオブジェクトや作業単位オブジェクトのような可能性のあるエラー状態を示す値をカプセル化する実際のオブジェクト。


16
「純粋」は単なる定義であることに注意してください。スローする関数が必要であるが、他のすべての方法で参照的に透過的である場合、それがあなたの書くものです。
ロバートハーベイ

1
Haskellで少なくともいずれかの機能を投げることができますが、あなたは、キャッチにIOにする必要があり、時にはスロー純粋な機能は、通常、部分的と呼ばれることになる
JK。

4
私はあなたのコメントのためにひらめきを感じました。私は純粋に知的に興味をそそられていますが、実際に私が実際に必要とするのは、決定論と敬transparencyな透明性です。純度を達成できれば、それは単なる追加ボーナスです。ありがとうございました。参考までに、私をこの道に導いたのは、Microsoft IntelliTestが決定論的なコードに対してのみ有効であるという事実です。
ロックアンソニージョンソン

4
@RockAnthonyJohnson:すべてのコードは確定的である か、少なくとも可能な限り確定的である必要があります。参照の透明性は、その決定性を得るための1つの方法にすぎません。スレッドなどの一部の手法は、その決定論に反する傾向があるため、スレッドモデルの非決定論的な傾向を改善するタスクなどの他の手法があります。乱数ジェネレーターやモンテカルロシミュレーターのようなものも決定論的ですが、確率に基づいて異なる方法です。
ロバートハーヴェイ

3
@zzzzBov:私は、「純粋な」の実際の厳密な定義をそれほど重要とは考えていません。上記の2番目のコメントを
ロバートハーベイ

12

まあ、はい...そしていいえ。

純粋な関数には参照の透明性が必要です。つまり、プログラムの動作を変更することなく、純粋な関数の呼び出しを戻り値に置き換えることができるはずです。*関数は特定の引数に対して常にスローすることが保証されているため、関数呼び出しを置き換える戻り値はないため、代わりに無視します。次のコードを検討してください。

{
    var ignoreThis = func(arg);
}

場合はfunc純粋で、オプティマイザはそれを決めることができましたfunc(arg)それをその結果で置き換えるができるとできます。結果が何であるかはまだわかりませんが、使用されていないことがわかります。そのため、このステートメントを推測するだけで効果はなく、削除できます。

しかし、もしfunc(arg)スローが起こると、このステートメントは何かをします-例外をスローします!そのため、オプティマイザはそれを削除できません-関数が呼び出されるかどうかは問題になりません。

しかし...

実際には、これはほとんど問題になりません。例外-少なくともC#では-は例外的なものです。通常の制御フローの一部として使用することは想定されていません。それを試行してキャッチする必要があります。何かをキャッチした場合、エラーを処理して、実行中の操作を元に戻すか、何らかの方法でそれを達成します。失敗するコードが最適化されて削除されたためにプログラムが正常に動作しない場合は、間違った例外を使用しています(テストコードではなく、テスト用にビルドする場合は例外を最適化しないでください)

言われていること...

キャッチされることを意図して純粋な関数から例外をスローしないでください-関数型言語がスタックアンワインド例外の代わりにモナドを使用することを好む十分な理由があります。

C#にErrorJava(および他の多くの言語)のようなクラスがある場合、のError代わりにをスローすることをお勧めしExceptionます。これは、関数のユーザーが何か間違ったことをした(スローする関数を渡した)ことを示し、そのようなことは純粋な関数で許可されています。しかし、C#にはErrorクラスがなく、使用法エラーの例外はから派生しているようですException。私の提案は、をスローするArgumentExceptionことで、関数が間違った引数で呼び出されたことを明確にします。


* 計算的に言えば。素朴な再帰を使用して実装されたフィボナッチ関数は、多数の場合に時間がかかり、マシンのリソースを使い果たす可能性がありますが、無限の時間とメモリがあるため、関数は常に同じ値を返し、副作用はありません(割り当て以外は)メモリとそのメモリの変更)-それはまだ純粋と見なされます。


1
+1 ArgumentExceptionの使用の提案、および「キャッチされることを意図して純粋な関数から例外をスローしない」ことを推奨します。
ロックアンソニージョンソン

最初の議論(「しかし...」まで)は私には不当なようです。例外は、関数のコントラクトの一部です。概念的に、任意の関数を書き換えて(value, exception) = func(arg)、例外をスローする可能性を削除できます(一部の言語および一部の種類の例外では、引数または戻り値と同じように、定義とともに実際にリストする必要があります)。これは以前と同じように純粋になります(同じ引数が与えられたときに例外をスローすることを常に返す場合)。この解釈を使用する場合、例外をスローすることは副作用ではありません
AnoE

@AnoEもしあなたがfuncそのように書いていたら、私が与えたブロックは最適化されたかもしれません。なぜならスタックを巻き戻す代わりに、スローされた例外はにエンコードされていたからignoreThisです。スタックを巻き戻す例外とは異なり、戻り値の型でエンコードされた例外は純度に違反しません。そのため、多くの関数型言語がそれらを使用することを好みます。
イダンアリー

まさに...あなたはこの想像された変換の例外を無視しなかっただろう。私はそれがセマンティクス/定義の少しの問題だと思います、私はただ例外の存在または発生が必ずしも純粋さのすべての概念を無効にしないことを指摘したかったです。
AnoE

1
@AnoEしかし、私はwrittedしたコードです。この想像変換のための例外を無視します。func(arg)タプルを返す(<junk>, TheException())と、そのignoreThisタプルが含まれています(<junk>, TheException())が、私たちは決して使用しないため、ignoreThis例外は何に影響を与えません。
イダンアリー

1

1つの考慮事項は、try-catchブロックが問題ではないことです。(上記の質問に対する私のコメントに基づく)。

主な問題は、NorthプロパティがI / O呼び出しであることです。

コードの実行のその時点で、プログラムはクライアントコードによって提供されるI / Oを確認する必要があります。(入力がデリゲートの形式であることや、名目上、入力が既に渡されていることは関係ありません)。

入力の制御を失うと、関数が純粋であることを保証できません。(特に関数がスローできる場合)。


Move [Check] Roomの呼び出しを確認したくない理由がわかりません。質問への私のコメントに従って:

はい。デリゲートを実行すると、最初にしか機能しない、または2回目の呼び出しで別の部屋に戻る場合はどうしますか デリゲートを呼び出すことは考慮されることができます/副作用自体を引き起こしますか?

バート・ファン・インゲン・シェナウが上で言ったように、

Create関数は、プロパティを取得するときに例外が発生するのを防ぎません。デリゲートがスローされる場合、実際には、特定の条件下でのみスローされる可能性が非常に高くなります。投げる条件は建設中に存在しない可能性がありますが、プロパティを取得するときに存在します。

一般に、どのタイプの遅延読み込み でも、その時点まで暗黙的にエラーが延期されます


Move [Check] Roomメソッドを使用することをお勧めします。これにより、不純なI / Oアスペクトを1つの場所に分離できます。

ロバート・ハーベイの答えに似ています:

それを純粋な関数にするには、関数からの期待値をカプセル化する実際のオブジェクトと、可能性のあるエラー状態を示す値(Maybeオブジェクトや作業単位オブジェクトなど)を返します。

入力から(可能性のある)例外を処理する方法を決定するのはコードライター次第です。その後、このメソッドはRoomオブジェクトまたはNull Roomオブジェクトを返すか、例外をバブルアウトすることができます。

この点は次の点に依存します。

  • ルームドメインは、ルーム例外をNullまたはその他の問題として扱いますか?
  • Null / Exception RoomでNorthを呼び出すクライアントコードに通知する方法。(保釈/ステータス変数/グローバル状態/モナドを返す/何でも;あるものは他のものより純粋です:))。

-1

うーん...私はこれについて正しくないと感じました。あなたがやろうとしていることは、他の人が何をしているのかを知らずに、自分の仕事を正しく行うようにすることだと思います。

関数はコンシューマーによって渡されるため、ユーザーまたは他の人が作成できます。

Roomオブジェクトが作成されたときに、関数パスインの実行が有効でない場合はどうなりますか?

コードから、私は北朝鮮が何をしているかを確信できませんでした。

機能が部屋の予約であり、予約の時間と期間が必要な場合、情報の準備ができていない場合は例外が発生します。

長時間実行される機能の場合はどうなりますか?Roomオブジェクトの作成中にプログラムをブロックしたくない場合、1000 Roomオブジェクトを作成する必要がある場合はどうなりますか?

あなたがこのクラスを使用するコンシューマを作成する人であれば、渡された関数が正しく作成されていることを確認しませんか?

消費者を書く他の人がいる場合、彼/彼女は部屋オブジェクトを作成する「特別な」条件を知らないかもしれません。

もう1つは、新しい例外をスローすることは必ずしも良いことではありません。理由は、元の例外の情報が含まれないことです。エラーが発生することはわかっていますが(一般的な例外のわかりやすい手段)、エラーの内容(null参照、範囲外のインデックス、ゼロ除算など)はわかりません。この情報は、調査を行う必要がある場合に非常に重要です。

例外の処理方法を知っている場合にのみ、例外を処理する必要があります。

元のコードは十分だと思います。唯一のことは、「メイン」(またはその処理方法を知っている任意のレイヤー)で例外を処理することです。


1
2番目のコード例をもう一度見てください。新しい例外を下位の例外で初期化します。スタックトレース全体が保持されます。
ロックアンソニージョンソン

おっとっと。私の間違い。
user251630

-1

他の答えには賛成せず、いいえと言います。例外が原因で純粋な関数が不純になることはありません。

例外の使用は、エラー結果の明示的なチェックを使用するように書き直すことができます。例外は、これに加えて構文上の砂糖と見なすことができます。便利な構文糖は純粋なコードを不純にしません。


1
不純物を書き直すことができるという事実は、関数を純粋にしません。Haskellは、不純な関数の使用が純粋な関数で書き換えられることの証明です。モナドを使用して、戻り型の純粋な純粋な関数の不純な動作をエンコードします。IOモナドの存在は通常のIOが純粋であることを意味しません-例外モナドの存在が通常の例外が純粋であることを意味するのはなぜですか?
イダンアリー

@IdanArye:そもそも不純物はないと主張しています。書き換えの例は、単にそれを実証するためのものです。通常のIOは、観察可能な副作用を引き起こすため、または外部入力のために同じ入力で異なる値を返す可能性があるため、不純です。例外をスローしても、これらのことは実行されません。
ジャックB

副作用のないコードでは不十分です。参照の透明性は、関数の純度の要件でもあります。
イダンアリー

1
決定論は参照の透明性にとって十分ではありません-副作用も決定論的です。参照の透明性とは、式をその結果で置き換えることができることを意味します-したがって、参照の透明な式を何回評価し、どの順序で、それらを評価するかどうかに関わらず、結果は同じである必要があります。例外が決定論的にスローされる場合、「何度でも」基準には適していますが、他の基準には適していません。私は自分の答えで「かどうか」の基準について議論しました(続き...)
イダンArye

1
(...続き)と、「どのような順序で」のためとして-場合fgの両方の純粋な関数であり、その後、f(x) + g(x)関係なく、あなたが評価するための同じ結果を持つべきであるf(x)g(x)。ただし、f(x)throws Fおよびg(x)throwsの場合G、この式からスローされる例外は、if が最初に評価され、if が最初に評価されるF場合f(x)です。これは、純粋な関数の動作方法ではありません!例外が戻り値の型にエンコードされると、その結果は評価順序とは無関係のようなものになります。Gg(x)Error(F) + Error(G)
イダンアリー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.