質問

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);;
                ^      ^
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top