我可以在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方式)。由于您不处理语句,而仅处理表达式,因此实际上没有什么需要突破的。通常,这由if
...then
.. else
语句的嵌套链处理。
就是说,我当然可以看到在哪里有足够的潜在出口点,因此长if
..then
.. else
链可能不太可读-尤其是在处理一些写入到外部API的情况下。返回错误代码,而不是在失败时抛出异常(例如Win32 API或某些COM组件),因此您确实需要该错误处理代码。如果是这样,似乎在F#中尤其要这样做,就是为此写一个workflow。这是我的第一个建议:
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
”的代码块“从头到尾”流动,您将获得运行时异常(到目前为止,我看不到有任何方法可以强制执行此操作)。
这有一些限制。首先,工作流确实还不完整-它允许您在内部使用let
,use
,if
,while
和for
,但不能使用try
.. with
或[C0 ] .. try
。可以完成-您需要实现finally
和Block.TryWith
-但到目前为止我还找不到它们的文档,因此这将需要一点猜测和更多时间。当我有更多时间时,我可能会再回来,并添加它们。
[其次,由于工作流实际上只是函数调用和lambda链的语法糖-特别是所有代码都在lambda中-您不能在工作流内部使用Block.TryFinally
。这就是为什么我在上面的示例代码中使用了let mutable
和ref
,这是通用的解决方法。
最后,由于所有的lambda调用,不可避免地会有性能损失。据推测,F#比C#(后者只保留IL中的所有内容)更擅长优化此类事情,并且可以在IL级别内嵌东西并做其他技巧。但是我对此了解不多,因此确切的性能影响(如果有的话)只能通过性能分析来确定。
[类似于Pavel的选项,但不需要您自己的工作流生成器,只是将您的代码块放在!
表达式中,并显示seq
错误消息。然后,在表达式之后,您只需调用yield
以获取第一条错误消息(或为null)。
由于序列表达式的计算是延迟的,这意味着它只会继续执行第一个错误的点(假设您只对序列中的FirstOrDefault
调用任何东西)。如果没有错误,那么它将一直持续到最后。因此,如果您以这种方式进行操作,就可以像早日返回一样思考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
我为自己发现了以下方法。基本上,它生成可能出口的let rec fib n =
if n < 2 then 1 else fib (n-2) + fib(n-1);;
^ ^
,其中每个出口由seq
生成,然后取seq的yield
仅返回首先计算的出口值。我不确定我们是否可以在某种程度上称这种方法“有效”。同样不确定这种方法的效率如何,以及如何在后台使用语言惰性。同样不确定多年后作者是否有需要。但是这种方式的一些优点是,代码看起来很像最初是在非功能性语言中看到的,而只使用了最少的一组固有功能。
请参阅下面的问题的第一个示例代码:
Seq.head
让我们进行一些测试呼叫:
let BadParam : int = -1
let Success : int = 0
let MyFunc param =
seq {
if (param < 0) then
printfn " bad param detected "
yield BadParam
// normal processing
printfn "normal processing"
yield Success
} |> Seq.head
让我们查看输出:
printfn "%i" (MyFunc 11)
printfn "%i" (MyFunc -11)
希望这个想法对可能对此有所困扰的人有所帮助。欢迎您就上述我的问题发表任何评论。