题
我可以在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);;
^ ^