14

最近は独学で F# を学んでおり、命令型 (C++/C#) のバックグラウンドを持っています。演習として、加算、乗算、行列式の取得など、行列を処理できる関数に取り組んできました。この点に関してはすべて順調に進んでいますが、処理に関して最善の決定を下していない可能性があります。無効な入力。例:

// 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  

この場合、マトリックスの周りに余分なレイヤーを追加するオプションを返しますが、関数の結果を使用することもできます。失敗した場合でも (None)、パターン マッチングなどで、プログラムの後の時点で使用できます。これらのタイプのシナリオのベスト プラクティスはありますか? 関数型プログラマーは何をしますか?

4

3 に答える 3

10

私は次の理由で例外を避ける傾向があります。

  • .NET 例外が遅い
  • 例外は、プログラムの制御フローを予期しない方法で変更するため、推論がはるかに難しくなります。
  • オプションを使用してフェールセーフを行うことができる一方で、重大な状況で例外が発生することがよくあります。

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

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 つは、関数が値を生成しなかった理由を示さないことです。ここではやり過ぎです。一般にChoice (または Haskell 用語では either モナド) は、エラー処理に適しています。

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# Core には Choice を操作するための高次関数がありません。これらの関数はFSharpXまたはExtCoreライブラリにあります。

于 2013-08-27T18:50:18.050 に答える
4

私は次のガイドラインに従う傾向があります。

予期しない問題が発生した場合、常に戻り値を持つと想定される関数で例外を使用します。これは、たとえば、引数が関数の規約に従わない場合に発生する可能性があります。これには、クライアント コードが単純になるという利点があります。

関数が有効な入力に対して戻り値を持つ場合がある場合は、Option を使用します。これは、たとえば、有効なキーが存在しない可能性があるマップ上での get である可能性があります。これにより、関数に戻り値があるかどうかをユーザーに確認させることができます。これによりバグが減る可能性がありますが、常にクライアント コードが乱雑になります。

あなたのケースはやや中間です。ディメンションが有効な場所で主に使用されると予想される場合は、例外をスローします。クライアントコードが無効なディメンションで頻繁に呼び出すと予想される場合は、オプションを返します。前者の方がきれいなので、おそらく前者を使用しますが(以下を参照)、コンテキストがわかりません:

// 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 を務めています。

于 2013-08-27T16:01:59.710 に答える