質問
C ++でこれを簡単に行うことができます(注:私はこれを正確性についてテストしませんでした-私がやろうとしていることを説明するためだけです):
const int BadParam = -1;
const int Success = 0;
int MyFunc(int param)
{
if(param < 0)
{
return BadParam;
}
//normal processing
return Success;
}
しかし、F#の早い段階でルーチンを終了する方法がわかりません。私がしたいのは、悪い入力で関数を終了するが、入力に問題がなければ続行することです。 F#のいくつかの基本的な特性が欠けていますか、それともFPを学習しているだけなので、間違った方法で問題に取り組んでいますか? ここで唯一のオプションは failwith ですか?
これは私がこれまでに手に入れたものであり、コンパイルは問題ありません:
#light
module test1
(* Define how many arguments we're expecting *)
let maxArgs = 2;;
(* The indices of the various arguments on the command line *)
type ProgArguments =
| SearchString = 0
| FileSpec = 1;;
(* Various errorlevels which the app can return and what they indicate *)
type ProgReturn =
| Success = 0
| WrongNumberOfArgumentsPassed = 1;;
[<EntryPoint>]
let main (args:string[]) =
printfn "args.Length is %d" args.Length
let ProgExitCode = if args.Length <> maxArgs then
printfn "Two arguments must be passed"
int ProgReturn.WrongNumberOfArgumentsPassed
(* Want to exit "main" here but how? *)
else
int ProgReturn.Success
let searchstring, filespec = args.[int ProgArguments.SearchString],args.[int ProgArguments.FileSpec];
printfn "searchstring is %s" searchstring
printfn "filespec is %s" filespec
ProgExitCode;;
この種のことを処理するFPの方法はありますか?
解決
私の意見では、マッチ式は、エラー状態を呼び出して個別に処理するための早期終了のF#の類似物です。あなたの例として、私は次のように書きます:
[<EntryPoint>]
let main (args:string[]) =
printfn "args.Length is %d" args.Length
match args with
| [| searchstring; filespace |] ->
// much code here ...
int Success
| _ -> printfn "Two arguments must be passed"
int WrongNumberOfArgumentsPassed
これにより、エラーのケースがうまく分離されます。一般に、何かの途中から抜け出す必要がある場合は、関数を分割し、エラーケースを match
に入れます。関数型言語での小さな関数の大きさに制限はありません。
余談ですが、整数定数のセットとして識別された共用体を使用するのは少し奇妙です。そのイディオムが好きなら、それらを参照するときに型名を含める必要がないことに注意してください。
他のヒント
F#では、すべてが式で構成されます(他の多くの言語では、キービルディングブロックはステートメントです)。関数を早期に終了する方法はありませんが、多くの場合、これは必要ありません。 Cでは、分岐がステートメントで構成される if / else
ブロックがあります。 F#には、 if / else
式があります。各ブランチはあるタイプの値に評価され、 if / else
式全体の値は1の値です。ブランチまたはその他。
つまり、このC ++:
int func(int param) {
if (param<0)
return BadParam;
return Success;
}
F#では次のようになります:
let func param =
if (param<0) then
BadParam
else
Success
コードは正しい軌道に乗っていますが、それをリファクタリングして、ロジックの大部分を else
ブランチに入れて、「アーリーリターン」で if
ブランチのロジック。
まず第一に、他の人がすでに指摘しているように、「F#の方法」ではありません。 (まあ、FP方法ではありません、本当に)。文ではなく式のみを扱うため、実際に抜け出すものはありません。一般的に、これは if
.. then
.. else
ステートメントのネストされたチェーンによって処理されます。
とはいえ、長い if
.. then
.. else
チェーンが可能な十分な潜在的な出口点があることは確かにわかります読みにくい-特に、失敗時に例外をスローするのではなく、エラーコードを返すように記述された外部API(Win32 APIやCOMコンポーネントなど)を扱う場合、そのエラー処理コードが本当に必要です。その場合、特にF#でこれを行う方法は、ワークフロー。
これが私の最初の試みです:
type BlockFlow<'a> =
| Return of 'a
| Continue
type Block() =
member this.Zero() = Continue
member this.Return(x) = Return x
member this.Delay(f) = f
member this.Run(f) =
match f() with
| Return x -> x
| Continue -> failwith "No value returned from block"
member this.Combine(st, f) =
match st with
| Return x -> st
| Continue -> f()
member this.While(cf, df) =
if cf() then
match df() with
| Return x -> Return x
| Continue -> this.While(cf, df)
else
Continue
member this.For(xs : seq<_>, f) =
use en = xs.GetEnumerator()
let rec loop () =
if en.MoveNext() then
match f(en.Current) with
| Return x -> Return x
| Continue -> loop ()
else
Continue
loop ()
member this.Using(x, f) = use x' = x in f(x')
let block = Block()
使用例:
open System
open System.IO
let n =
block {
printfn "Type 'foo' to terminate with 123"
let s1 = Console.ReadLine()
if s1 = "foo" then return 123
printfn "Type 'bar' to terminate with 456"
let s2 = Console.ReadLine()
if s2 = "bar" then return 456
printfn "Copying input, type 'end' to stop, or a number to terminate with that number"
let s = ref ""
while (!s <> "end") do
s := Console.ReadLine()
let (parsed, n) = Int32.TryParse(!s)
if parsed then
printfn "Dumping numbers from 1 to %d to output.txt" n
use f = File.CreateText("output.txt") in
for i = 1 to n do
f.WriteLine(i)
return n
printfn "%s" s
}
printfn "Terminated with: %d" n
ご覧のとおり、 return
に遭遇するとすぐに、ブロックの残りの部分も評価されないような方法で、すべての構成要素を効果的に定義します。ブロックが「最後から」流れた場合 return
がないと、ランタイム例外が発生します(これまでのところ、コンパイル時にこれを強制する方法は見当たりません)。
これにはいくつかの制限があります。まず第一に、ワークフローは実際には完了していません- let
、 use
、 if
、 while
および for
内、ただし try
.. with
または try
.. finally
。それを行うことができます- Block.TryWith
と Block.TryFinally
を実装する必要があります-しかし、私はそれらのドキュメントを今のところ見つけることができないので、これには少し必要ですちょっとした推測とより多くの時間。後で時間があるときに戻って追加するかもしれません。
第二に、ワークフローは実際には一連の関数呼び出しとラムダの単なる構文上の砂糖であり、特にすべてのコードはラムダであるため、ワークフロー内で let mutable
を使用することはできません。上記のサンプルコードで ref
および!
を使用した理由は、汎用的な回避策です。
最後に、すべてのラムダ呼び出しのために、パフォーマンスのペナルティは避けられません。おそらく、F#はそのようなことを最適化するのがC#(これはすべてILのままである)よりも優れており、ILレベルでインライン化して他のトリックを実行できます。しかし、私はそれについてあまり知らないので、正確なパフォーマンスヒットは、もしあれば、プロファイリングによってのみ決定できました。
Pavelに似たオプションですが、独自のワークフロービルダーを必要とせずに、コードブロックを seq
式内に配置し、 yield
エラーメッセージを表示するだけです。次に、式の直後に FirstOrDefault
を呼び出して、最初のエラーメッセージ(またはnull)を取得します。
シーケンス式は遅延評価されるため、最初のエラーのポイントにのみ進むことを意味します(シーケンスの FirstOrDefault
以外は呼び出さないと仮定)。エラーがなければ、最後まで実行されます。したがって、この方法で行うと、 yield
を早期のリターンと同じように考えることができます。
let x = 3.
let y = 0.
let errs = seq {
if x = 0. then yield "X is Zero"
printfn "inv x=%f" (1./x)
if y = 0. then yield "Y is Zero"
printfn "inv y=%f" (1./y)
let diff = x - y
if diff = 0. then yield "Y equals X"
printfn "inv diff=%f" (1./diff)
}
let firstErr = System.Linq.Enumerable.FirstOrDefault errs
if firstErr = null then
printfn "All Checks Passed"
else
printfn "Error %s" firstErr
この再帰フィボナッチ関数には2つの出口点があります:
let rec fib n =
if n < 2 then 1 else fib (n-2) + fib(n-1);;
^ ^