2013-08-27 18 views
11

最近私はF#を教えてきました。私は命令的な(C++/C#)のバックグラウンドから来ています。練習として、私は行列を使って、行列を追加、乗算、行列式などを得ることができる関数に取り組んできました。この点に関してはすべてがうまくいっていますが、扱いに関係するときには、例えば、無効な入力、:F#:Some、None、またはException?

// I want to multiply two matrices 
let mult m1 m2 = 
    let sizeOK = validateDims m1 m2 

    // Here is where I am running to conceptual trouble: 
    // In a C# world, I would throw an exception. 
    if !sizeOK then 
    raise (InvalidOperationException("bad dimensions!") 
    else 
    doWork m1 m2 

だから、これは技術的に動作しますが、これは関数型言語に適しているのですか?関数型プログラミングの精神ですか?私も失敗に、マトリックスの周りに余分なレイヤを追加しますが、私は、関数の結果を使用することができますオプションを、帰国しています。この場合

let mult m1 m2 = 
    let sizeOK = validateDims m1 m2 

    if !sizeOK then 
    None 
    else 
    Some doWork m1 m2 

:として、あるいはそれを書き換えるために、より理にかなってパターンマッチングのあるケース(なし)などがあります。このようなシナリオにはベストプラクティスがありますか?機能的なプログラマは何をしますか?

+0

私は後者がFPの「精神」にあると考えています。 Maybeモナドだけでなく、悪い入力を処理するための一般的なアプローチを調べることができます。 –

+0

Padが答えて言ったように、私はしばしばエラーをスローする関数とスローするのではなくOption/Choiceを返す同じtry-functionの2つの関数を実装します。私は最終的なコードを最も美しく見せるものを使用しています:)あなたが "鉄道指向のプログラミング"を読んでみたいかもしれないオプション/選択肢の処理のために:http://fsharpforfunandprofit.com/posts/recipe-part2/ – stmax

答えて

8

は、私は次のような理由で例外を回避する傾向がある:

  • .NET exceptions are slow
  • 例外はそれがはるかに困難で発生する程度
  • 例外が多い理由になります予期しない方法でプログラムの制御フローを変更しますオプションを使用することでフェールセーフが可能です。

あなたのケースでは、私は(など、例えばList.tryFindList.find)F#コアライブラリの慣習に従い、両方のバージョンを作成します。

let tryMult m1 m2 = 
    let sizeOK = validateDims m1 m2 

    if not sizeOK then 
    None 
    else 
    Some <| doWork m1 m2 

let mult m1 m2 = 
    let sizeOK = validateDims m1 m2 

    if not sizeOK then 
    raise <| InvalidOperationException("bad dimensions!") 
    else 
    doWork m1 m2 

この例では、例外を使用するのに十分例外ではありません。 mult関数は、C#との互換性のために含まれています。あなたのライブラリをC#で使用している人は、オプションを簡単に分解するためのパターンマッチングを持っていません。

オプションの欠点の1つは、関数が値を生成しなかった理由がないことです。それはここで過労です。 (Haskellの期間中またはいずれかのモナド)一般Choiceerror handlingに適している:

let tryMult m1 m2 = 
    // Assume that you need to validate input 
    if not (validateInput m1) || not (validateInput m2) then 
    Choice2Of2 <| ArgumentException("bad argument!") 
    elif not <| validateDims m1 m2 then 
    Choice2Of2 <| InvalidOperationException("bad dimensions!") 
    else 
    Choice1Of2 <| doWork m1 m2 

それは、F#コアは選択肢を操作するために、高次の機能を欠いていることは残念です。これらの関数はFSharpXまたはExtCoreライブラリにあります。

3

私は、次のガイドラインに行く傾向がある:何かが突然おかしくなったときは、常に、戻り値を持っていると思われる機能で

使用例外。これは、例えば、引数が関数の契約に従わない場合にはこれには、クライアントコードがより簡単になるという利点があります。

関数が有効な入力に対して戻り値を持つことがある場合は、オプションを使用します。これは、例えば、有効なキーが存在しない可能性があるマップを取得してください。これにより、関数に戻り値があるかどうかをユーザーに確認させます。これはバグを減らすかもしれないが、常にクライアントコードを混乱させる。

あなたの場合は、多少の間です。主にディメンションが有効な場所で使用されることが予想される場合は、例外がスローされます。 クライアントコードで無効なディメンションを呼び出すことが多いと思われる場合は、Optionを返します。それはきれいであるように私はおそらく、かつて一緒に行きます(下記参照)が、私はあなたの状況を知らない:

// With exception 
let mult3 a b c = 
    mult (mult a b) c; 

// With option 
let mult3 a b c= 
    let option = mult a b 
    match option with 
    | Some(x) -> mult x b 
    | None -> None 

免責事項:私は関数型プログラミングとは専門的な経験を持っていませんが、私は大学院レベルのF#プログラミングのTA。

2

私は上記の回答が好きですが、私は別のオプションを追加したいと思います。それは実際には予期しない結果と、それが進んでいく意味があるかどうかによって異なります。まれなイベントで、呼び出し元が失敗する可能性がない場合、例外は完全に尊重されます。例外をキャッチするコードは、上記のレベルが多い可能性があり、呼び出し元はおそらく失敗する予定はありませんでした。操作が失敗することが本当に日常的な結果である場合、Some/NoneはOKですが、2つのオプションと結果を渡す方法はありません。もう一つの選択肢は、識別可能な可能性の組み合わせを作ることです。これは、呼び出し元が異なる結果に一致する可能性が高く、拡張可能であり、すべての結果を同じデータ型にするよう強制しません。

type MultOutcome = 
    | RESULT of Matrix 
    | DIMERROR 
    | FOOERROR of string 


let mult a b = 
    if dimensionsWrong then 
     DIMERROR 
    elif somethingElseIDoNotLike then 
     FOOERROR("specific message") 
    else 
     DIMRESULT(a*b) 


match mult x y with 
    | DIMERROR -> printfn "I guess I screwed up my matricies" 
    | FOOERROR(s) -> printfn "Operation failed with message %s" s 
    | DIMRESULT(r) -> 
     // Proceed with result r 
関連する問題