我可以在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 表达式的值是一个值分支或其他。

所以这个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方式,真的)。既然你不处理语句,只处理表达式,那么就没有什么可以突破的。通常,这由 .. 然后 .. else 语句的嵌套链处理。

那说,我当然可以看到哪里有足够的潜在退出点,如果 .. 那么 .. else 链就可以了很长的是不是很可读 - 特别是在处理一些外部API时,这些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 ,你将得到一个运行时异常(到目前为止,我没有看到任何在编译时强制执行此操作的方法)。

这有一些限制。首先,工作流程真的不完整 - 它允许你使用使用 if while for ,但尝试 .. with try .. finally 。它可以完成 - 您需要实现 Block.TryWith Block.TryFinally - 但到目前为止我找不到它们的文档,所以这需要一点点有点猜测和更多的时间。我可能会在以后有更多时间回来,并添加它们。

其次,由于工作流实际上只是函数调用和lambdas链的语法糖 - 特别是所有代码都在lambda中 - 你不能在工作流中使用 let mutable 。这就是我在上面的示例代码中使用 ref 的原因,这是一般的解决方法。

最后,由于所有lambda调用,都存在不可避免的性能损失。据说,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

这个递归的Fibonacci函数有两个出口点:

let rec fib n =
  if n < 2 then 1 else fib (n-2) + fib(n-1);;
                ^      ^
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top