Question

Je pouvais le faire facilement en C ++ (note: je n'ai pas testé cela pour l'exactitude - c'est uniquement pour illustrer ce que j'essaie de faire):

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

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

      //normal processing

      return Success;
   }

Mais je n'arrive pas à comprendre comment sortir d'une routine au début de F #. Ce que je veux faire, c'est quitter la fonction sur une mauvaise entrée mais continuer si l'entrée est correcte. Est-ce que je manque une propriété fondamentale de F # ou est-ce que j'aborde le problème de manière erronée puisque je viens d'apprendre la PF? Est-ce qu'un failwith est ma seule option ici?

C’est ce que j’ai eu jusqu’à présent et ça compile bien:

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

Existe-t-il un moyen de PF de traiter ce genre de chose?

Était-ce utile?

La solution

À mon avis, les expressions de correspondance sont l'analogue F # de sortie anticipée permettant d'appeler des conditions erronées et de les traiter séparément. Pour votre exemple, j'écrirais:

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

Ceci sépare bien le cas d'erreur. En général, si vous devez sortir du milieu de quelque chose, divisez les fonctions puis placez le cas d'erreur dans une correspondance . Il n'y a vraiment aucune limite à la façon dont les petites fonctions devraient être dans un langage fonctionnel.

En passant, votre utilisation de unions discriminées en tant qu'ensembles de constantes de nombre entier est un peu étrange. Si vous aimez cet idiome, sachez que vous n'avez pas besoin d'inclure le nom du type lorsque vous vous référez à eux.

Autres conseils

En F #, tout est composé d'expressions (alors que dans de nombreux autres langages, le bloc de construction de clé est une déclaration). Il n'y a aucun moyen de quitter une fonction tôt, mais souvent cela n'est pas nécessaire. En C, vous avez un bloc if / else où les branches sont constituées d'instructions. En F #, il y a une expression if / else , où chaque branche est évaluée à une valeur d'un type, et la valeur de l'expression if / else entière est la valeur d'un branche ou l'autre.

Donc ce C ++:

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

Ressemble à ceci en fa:

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

Votre code est sur la bonne voie, mais vous pouvez le refactoriser, en plaçant l'essentiel de votre logique dans la branche else , avec le signe "retour anticipé". logique dans la branche si .

Tout d'abord, comme d'autres l'ont déjà noté, ce n'est pas "la manière F #". (bon, pas moyen, vraiment). Puisque vous ne traitez pas avec des déclarations, mais seulement avec des expressions, il n'y a pas vraiment de quoi s'échapper. En général, cela est traité par une chaîne imbriquée de si .. then .. else instructions.

Cela dit, je peux certainement voir où il y a suffisamment de points de sortie potentiels qu'une longue chaîne si .. puis .. sinon peut ne soyez pas très lisible - en particulier lorsque vous manipulez une API externe qui est écrite pour renvoyer des codes d'erreur plutôt que de lever des exceptions en cas d'échec (par exemple, une API Win32 ou un composant COM), vous avez donc vraiment besoin de ce code de gestion des erreurs. Si tel est le cas, il semble que le moyen de le faire en F # en particulier serait d'écrire un flux de travail pour cela. Voici ma première prise de conscience:

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

Exemple d'utilisation:

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

Comme vous pouvez le constater, il définit efficacement toutes les constructions de telle sorte que, dès que return est rencontré, le reste du bloc n'est même pas évalué. Si le flux est bloqué "à la fin" sans return , vous obtiendrez une exception d'exécution (je ne vois aucun moyen de l'appliquer à la compilation jusqu'à présent).

Cela vient avec quelques limitations. Tout d’abord, le flux de travail n’est vraiment pas complet - il vous permet d’utiliser laissez , utiliser , si , tant que et pour à l'intérieur, mais pas essayer .. avec ou essayer .. finalement . Cela peut être fait - vous devez implémenter Block.TryWith et Block.TryFinally - mais je ne trouve pas la documentation pour eux jusqu'à présent, cela nécessitera donc un peu peu de devinettes et plus de temps. Je pourrais y revenir plus tard, quand j'aurai plus de temps, et les ajouter.

Deuxièmement, puisque les flux de travail ne sont en réalité que du sucre syntaxique pour une chaîne d'appels de fonctions et de lambdas - et, en particulier, tout votre code est dans lambdas - vous ne pouvez pas utiliser let mutable dans le flux de travail. C'est pourquoi j'ai utilisé ref et ! dans l'exemple de code ci-dessus, qui constitue la solution de contournement à usage général.

Enfin, il y a la pénalité de performance inévitable à cause de tous les appels lambda. Soi-disant, F # est meilleur pour l'optimisation de telles choses que, disons, C # (qui laisse simplement tout ce qu'il est dans IL), et peut aligner des choses au niveau de IL et faire d'autres astuces; mais je ne sais pas grand-chose à ce sujet, de sorte que le rendement exact, le cas échéant, ne peut être déterminé que par le profilage.

Une option similaire à celle de Pavel, mais qui n’a pas besoin de votre propre générateur de flux de travail, consiste simplement à placer votre bloc de code dans une expression seq et à lui faire donner des messages d’erreur . Ensuite, juste après l'expression, il vous suffit d'appeler FirstOrDefault pour obtenir le premier message d'erreur (ou null).

Etant donné qu'une expression de séquence évalue paresseusement, cela signifie qu'elle ne procédera qu'au point de la première erreur (en supposant que vous n'appeliez jamais autre chose que FirstOrDefault sur la séquence). Et s’il n’ya pas d’erreur, il se poursuit jusqu’à la fin. Donc, si vous le faites de cette façon, vous pourrez penser à rendement comme à un retour anticipé.

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

Cette fonction de Fibonacci récursive a deux points de sortie:

let rec fib n =
  if n < 2 then 1 else fib (n-2) + fib(n-1);;
                ^      ^
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top