Frage

Ich kann dies leicht in C ++ tun (Anmerkung: Ich habe das nicht für Richtigkeit prüfen - es ist nur zu zeigen, was ich tun werde versuchen):

   const int BadParam = -1;
   const int Success = 0;

   int MyFunc(int param)
   {
      if(param < 0)
      {
         return BadParam;
      }

      //normal processing

      return Success;
   }

Aber ich kann nicht herausfinden, wie eine Routine früh in F # zu verlassen. Was ich tun möchte, ist die Funktion an einem schlechten Eingang zu verlassen, aber weiterhin, wenn der Eingang in Ordnung ist. Bin ich eine grundlegende Eigenschaft von F # fehle, oder bin ich das Problem in der falschen Art und Weise nähern, da ich FP bin nur das Lernen? Ist ein failwith meine einzige Option hier?

Das ist, was ich so weit gekommen und es kompiliert ok:

   #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;;

Gibt es eine FP Art und Weise mit dieser Art der Sache umzugehen?

War es hilfreich?

Lösung

Meiner Meinung nach, Spiel Ausdrücke sind die F # analog früh Ausgang für fehlerhafte Bedingungen zu rufen und sie getrennt behandeln. Für Ihr Beispiel, würde ich schreiben:

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

Diese trennt den Fehlerfall schön. In der Regel müssen, wenn Sie von der Mitte von etwas, Split-Funktionen verlassen und dann in einem match den Fehlerfall setzen. Es gibt wirklich keine Grenze, wie kleine Funktionen in einer funktionalen Sprache sein sollen.

Als beiseite, um Ihre Nutzung der diskriminierte Gewerkschaften als Sätze von Integer-Konstanten ist ein wenig seltsam. Wenn Sie möchten, dass Idiom, bewusst sein, dass Sie die Typnamen nicht enthalten müssen, wenn sie Bezug genommen wird.

Andere Tipps

In F #, die alles von Ausdrücken aus (während in vielen anderen Sprachen ist der Schlüsselbaustein eine Anweisung). Es gibt keine Möglichkeit, eine Funktion vorzeitig zu beenden, aber oft dies erforderlich ist, nicht. In C haben Sie if/else Blöcke, wo die Zweige sind aus Aussagen. In F # gibt es eine if/else Ausdruck, wobei jeder Zweig auf einen Wert von irgendeiner Art auswertet, und der Wert des gesamten if/else Ausdrucks ist der Wert eines Zweiges oder das andere.

Also die C ++:

int func(int param) {
  if (param<0)
    return BadParam;
  return Success;
}

wie folgt aussieht in F #:

let func param =
  if (param<0) then
    BadParam
  else
    Success

Der Code ist auf dem richtigen Weg, aber man kann es Refactoring, die meisten Ihre Logik in dem else Zweig setzen, mit der „early return“ Logik in dem if Zweig.

Zunächst einmal, wie andere bereits festgestellt haben, ist es nicht „die F # way“ (na ja, nicht FP Weg, wirklich). Da Sie mit Aussagen nicht beschäftigen, sondern nur Ausdrücke, gibt es nicht wirklich etwas aus zu brechen. Im Allgemeinen wird dies durch eine verschachtelte Kette von if..then..else Anweisungen behandelt.

Das heißt, ich kann sicherlich sehen, wo es genügend Potential Ausspeisepunkte, dass eine lange if..then..else Kette nicht sehr gut lesbar sein kann - insbesondere dann, wenn mit einer externen API handelt, die Fehlercodes geschrieben zurückzukehren, anstatt werfen Ausnahmen auf Ausfälle (sagen Win32-API oder eine COM-Komponente), so müssen Sie wirklich, dass die Fehlerbehandlung Code. Wenn ja, so scheint es, die Art und Weise dies insbesondere in F # zu tun wäre, ein Workflow für sie. Hier ist meine erste Klappe es:

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() 

Nutzungs Beispiel:

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

Wie Sie sehen können, ist es definiert effektiv alle Konstrukte in der Weise, dass, sobald return angetroffen wird, wird der Rest des Blocks nicht einmal ausgewertet. Wenn der Block ohne return „vom Ende“ fließt, werden Sie eine Laufzeitausnahme (ich keine Möglichkeit sehe diese zum Zeitpunkt der Kompilierung zu erzwingen bisher).

Dieses kommt mit einigen Einschränkungen. Zunächst einmal ist der Workflow wirklich nicht abgeschlossen - es lässt Sie let, use, if, while und for innen verwenden, aber try..with oder try..finally nicht. Es kann getan werden - müssen Sie Block.TryWith und Block.TryFinally implementieren - aber ich kann nicht die Dokumentation für sie so weit finden, so dass dies ein wenig zu raten und mehr Zeit benötigen. Ich könnte später darauf zurückkommen, wenn ich mehr Zeit habe, und fügen Sie sie.

Zweitens, da Workflows wirklich nur syntaktischer Zucker für eine Kette von Funktionsaufrufen und Lambda-Ausdrücke sind - und vor allem den gesamten Code ist in lambdas - Sie nicht let mutable innerhalb des Workflow verwenden können. Es ist, warum ich ref und ! in dem obigen Beispielcode verwendet habe, die die allgemeinen Zweck Abhilfe ist.

Schließlich gibt es noch die unvermeidliche Leistungseinbuße wegen all der Lambda-Anrufe. Angeblich ist F # besser auf solche Dinge zu optimieren, als, sagen C # (die gerade alles verlässt, wie in IL), und Material auf IL-Ebene Inline tun können und andere Tricks; aber ich weiß nicht viel darüber, so die genaue Performance-Einbußen, wenn überhaupt, nur durch Profilierung bestimmt werden.

Eine Option ähnlich wie Pavels, aber ohne Ihren eigenen Workflow Builder zu benötigen, ist nur der Code-Block in einem seq Ausdruck zu bringen, und es yield Fehlermeldungen haben. Dann direkt nach dem Ausdruck, die Sie gerade FirstOrDefault rufen Sie die erste Fehlermeldung (oder null) zu erhalten.

Da eine Sequenz Ausdruck lazily auswertet, das bedeutet, es wird nur bis zu dem Punkt des ersten Fehlers gehen (vorausgesetzt, Sie nie etwas anderes als FirstOrDefault auf der Sequenz nennen). Und wenn es kein Fehler ist, dann läuft es einfach bis zum Ende. Also, wenn Sie es auf diese Weise tun Sie in der Lage sein, von yield zu denken, wie eine baldige Rückkehr.

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

Diese rekursive Fibonacci-Funktion hat zwei Ausgangspunkte:

let rec fib n =
  if n < 2 then 1 else fib (n-2) + fib(n-1);;
                ^      ^
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top