Mehrere Exits Von # Funktion F
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?
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);;
^ ^