Pregunta

Podría hacer esto fácilmente en C ++ (nota: no lo probé para ver si está correcto, es solo para ilustrar lo que estoy tratando de hacer):

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

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

      //normal processing

      return Success;
   }

Pero no puedo averiguar cómo salir de una rutina al principio de F #. Lo que quiero hacer es salir de la función en una entrada incorrecta pero continuar si la entrada es correcta. ¿Me estoy perdiendo alguna propiedad fundamental de F # o me estoy acercando al problema de forma incorrecta ya que estoy aprendiendo FP? Es un failwith mi única opción aquí?

Esto es lo que tengo hasta ahora y se compila:

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

¿Hay alguna manera de tratar este tipo de cosas con la FP?

¿Fue útil?

Solución

En mi opinión, las expresiones de coincidencia son el análogo de F # de salida temprana para indicar condiciones erróneas y manejarlas por separado. Para tu ejemplo, escribiría:

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

Esto separa el caso de error muy bien. En general, si necesita salir de la mitad de algo, dividir las funciones y luego colocar el caso de error en una coincidencia . Realmente no hay límite a cómo deben ser las funciones pequeñas en un lenguaje funcional.

Como nota aparte, su uso de uniones discriminadas como conjuntos de constantes enteras es un poco extraño. Si le gusta ese idioma, tenga en cuenta que no necesita incluir el nombre de tipo cuando se refiera a ellos.

Otros consejos

En F #, todo está formado por expresiones (mientras que en muchos otros idiomas, el bloque de construcción clave es una declaración). No hay manera de salir de una función temprano, pero a menudo esto no es necesario. En C, tiene un if / else bloques donde las sucursales están formadas por declaraciones. En F #, hay una expresión if / else , donde cada rama se evalúa a un valor de algún tipo, y el valor de la expresión completa if / else es el valor de uno rama o la otra.

Así que este C ++:

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

Se ve así en F #:

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

Su código está en el camino correcto, pero puede refactorizarlo, poniendo la mayor parte de su lógica en la rama else , con el " retorno anticipado " lógica en la rama si .

En primer lugar, como ya han dicho otros, no es " la forma F # " (bueno, no de manera FP, de verdad). Ya que no se trata de declaraciones, sino de expresiones, no hay realmente nada que romper. En general, esto se trata mediante una cadena anidada de if .. entonces .. else .

Dicho esto, ciertamente puedo ver dónde hay suficientes puntos de salida potenciales que un largo if .. luego .. else puede no debe ser muy legible, especialmente cuando se trata de una API externa que está escrita para devolver códigos de error en lugar de lanzar excepciones en fallas (por ejemplo, la API de Win32 o algún componente COM), por lo que realmente necesita ese código de manejo de errores. Si es así, parece que la forma de hacerlo en F # en particular sería escribir un flujo de trabajo para ello. Aquí está mi primera toma:

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

Ejemplo de uso:

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

Como puede ver, define efectivamente todas las construcciones de tal manera que, tan pronto como se encuentre return , el resto del bloque ni siquiera se evalúa. Si el bloque fluye " fuera del final " sin un return , obtendrás una excepción de tiempo de ejecución (hasta ahora no veo ninguna forma de aplicar esto en tiempo de compilación).

Esto viene con algunas limitaciones. En primer lugar, el flujo de trabajo realmente no está completo: le permite usar let , use , if , mientras que y para adentro, pero no intente .. con o intente .. finalmente . Se puede hacer - necesita implementar Block.TryWith y Block.TryFinally - pero no puedo encontrar los documentos para ellos hasta ahora, así que esto necesitará un poco. Poco de adivinar y más tiempo. Podría volver a ello más tarde, cuando tenga más tiempo, y agregarlos.

Segundo, dado que los flujos de trabajo son realmente solo azúcar sintáctica para una cadena de llamadas a funciones y lambdas, y, en particular, todo su código está en lambdas, no puede usar let mutable dentro del flujo de trabajo. Es por eso que he usado ref y ! en el código de ejemplo anterior, que es la solución de propósito general.

Finalmente, está la inevitable penalización de rendimiento debido a todas las llamadas de Lambda. Supuestamente, F # es mejor para optimizar tales cosas que, digamos C # (que deja todo como está en IL), y puede incluir cosas en el nivel de IL y hacer otros trucos; pero no sé mucho al respecto, por lo que el impacto exacto en el rendimiento, si lo hubiera, solo podría determinarse mediante la creación de perfiles.

Una opción similar a la de Pavel, pero sin necesitar su propio generador de flujo de trabajo, es simplemente colocar el bloque de código dentro de una expresión seq , y hacer que produzca mensajes de error. Luego, justo después de la expresión, simplemente llame a FirstOrDefault para obtener el primer mensaje de error (o nulo).

Dado que una expresión de secuencia se evalúa perezosamente, eso significa que solo procederá al punto del primer error (asumiendo que nunca se llama nada excepto FirstOrDefault en la secuencia). Y si no hay error, simplemente se ejecuta hasta el final. Por lo tanto, si lo hace de esta manera, podrá pensar en yield como un retorno anticipado.

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

Esta función de Fibonacci recursiva tiene dos puntos de salida:

let rec fib n =
  if n < 2 then 1 else fib (n-2) + fib(n-1);;
                ^      ^
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top