F#函数多次退出

Ono*_*cci 12 f# idioms structure

我可以在C++中轻松地做到这一点(注意:我没有测试它的正确性 - 它只是为了说明我正在尝试做什么):

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

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

      //normal processing

      return Success;
   }
Run Code Online (Sandbox Code Playgroud)

但我无法弄清楚如何在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;;
Run Code Online (Sandbox Code Playgroud)

有没有FP方式来处理这类事情?

kvb*_*kvb 10

在F#中,一切都由表达式组成(而在许多其他语言中,关键构建块是一个声明).没有办法尽早退出功能,但通常不需要这样做.在C中,您有一个if/else块,其中分支由语句组成.在F#中,有一个if/else表达式,其中每个分支求值为某种类型的值,整个if/else表达式的值是一个分支或另一个分支的值.

所以这个C++:

int func(int param) {
  if (param<0)
    return BadParam;
  return Success;
}
Run Code Online (Sandbox Code Playgroud)

在F#中看起来像这样:

let func param =
  if (param<0) then
    BadParam
  else
    Success
Run Code Online (Sandbox Code Playgroud)

您的代码在正确的轨道上,但您可以重构它,将大部分逻辑放在else分支中,并在分支中使用"早期返回"逻辑if.


Nat*_*ers 5

在我看来,匹配表达式是早期退出的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
Run Code Online (Sandbox Code Playgroud)

这很好地区分了错误情况.一般情况下,如果你需要退出某些东西的中间,拆分函数,然后将错误案例放在一个match.功能语言中的小功能应该没有限制.

顺便说一下,你使用有区别的联合作为整数常量的集合有点奇怪.如果您喜欢这种习语,请注意在引用它们时不需要包含类型名称.


Pav*_*aev 5

首先,正如其他人已经指出的那样,这不是“F# 方式”(嗯,不是 FP 方式,真的)。由于您不处理语句,而只处理表达式,因此实际上没有什么可以突破的。一般来说,这是通过嵌套的if.. then..else语句链来处理的。

也就是说,我当然可以看到哪里有足够的潜在退出点,长的if.. then..else链可能不太可读 - 特别是在处理一些编写为返回错误代码而不是在失败时抛出异常的外部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() 
Run Code Online (Sandbox Code Playgroud)

使用示例:

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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,它有效地定义了所有构造,一旦return遇到,块的其余部分甚至不会被评估。如果块在没有 的情况下“结束”流动return,您将得到运行时异常(到目前为止,我没有看到任何方法可以在编译时强制执行此操作)。

这有一些限制。首先,工作流程确实不完整 - 它允许您在内部使用let, use, if,whilefor,但不能使用try..withtry.. finally。这是可以完成的 - 你需要实现Block.TryWith-Block.TryFinally但到目前为止我找不到它们的文档,所以这需要一点猜测和更多时间。当我有更多时间时,我可能会回过头来添加它们。

其次,由于工作流实际上只是一系列函数调用和 lambda 的语法糖 - 特别是,所有代码都在 lambda 中 - 您不能let mutable在工作流内部使用。这就是为什么我在上面的示例代码中使用了refand !,这是通用的解决方法。

最后,由于所有 lambda 调用,不可避免地会出现性能损失。据推测,F# 比 C# 更擅长优化此类事情(C# 只保留 IL 中的所有内容),并且可以在 IL 级别内联内容并执行其他技巧;但我对此了解不多,因此确切的性能影响(如果有的话)只能通过分析来确定。