F# 函数的多次退出
我可以在 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,所以我是否以错误的方式处理问题? 失败是我唯一的选择吗?
这是我到目前为止所得到的,并且编译正常:
#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 方法来处理这类事情?
I could do this easily in C++ (note: I didn't test this for correctness--it's only to illustrate what I'm trying to do):
const int BadParam = -1;
const int Success = 0;
int MyFunc(int param)
{
if(param < 0)
{
return BadParam;
}
//normal processing
return Success;
}
But I cannot figure out how to exit a routine early in F#. What I want to do is to exit the function on a bad input but continue if the input is ok. Am I missing some fundamental property of F# or am I approaching the problem in the wrong way since I'm just learning FP?
Is a failwith my only option here?
This is what I've got so far and it compiles ok:
#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;;
Is there an FP way of dealing with this sort of thing?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
在 F# 中,一切都由表达式组成(而在许多其他语言中,关键构建块是语句)。没有办法提前退出函数,但通常不需要这样做。在 C 中,有一个
if/else
块,其中分支由语句组成。在 F# 中,有一个if/else
表达式,其中每个分支的计算结果为某种类型的值,整个if/else
表达式的值为 1分支或其他。所以这个 C++:
在 F# 中看起来像这样:
您的代码处于正确的轨道上,但您可以重构它,将大部分逻辑放在
else
分支中,并将“提前返回”逻辑放在if
分支。In F#, everything's made up of expressions (whereas in many other languages, the key building block is a statement). There's no way to exit a function early, but often this isn't needed. In C, you have an
if/else
blocks where the branches are made up of statements. In F#, there's anif/else
expression, where each branch evaluates to a value of some type, and the value of the entireif/else
expression is the value of one branch or the other.So this C++:
Looks like this in F#:
Your code is on the right track, but you can refactor it, putting most of your logic in the
else
branch, with the "early return" logic in theif
branch.在我看来,匹配表达式类似于 F# 的提前退出,用于调出错误条件并单独处理它们。对于你的例子,我会写:
这很好地区分了错误情况。一般来说,如果您需要从中途退出,请拆分函数,然后将错误情况放入
match
中。在函数式语言中,函数的大小确实没有限制。顺便说一句,您使用可区分的联合作为整数常量集有点奇怪。如果您喜欢这个习惯用法,请注意在引用它们时不需要包含类型名称。
In my opinion, match expressions are the F# analogue of early-exit for calling out erroneous conditions and handling them separately. For your example, I'd write:
This separates the error case nicely. In general, if you need to exit from the middle of something, split functions and then put the error case in a
match
. There's really no limit to how small functions should be in a functional language.As an aside, your use of discriminated unions as sets of integer constants is a little weird. If you like that idiom, be aware that you don't need to include the type name when referring to them.
首先,正如其他人已经指出的那样,这不是“F# 方式”(嗯,不是 FP 方式,真的)。由于您不处理语句,而只处理表达式,因此实际上没有什么可以突破的。一般来说,这是通过嵌套的
if
..then
..else
语句链来处理的。也就是说,我当然可以看到哪里有足够的潜在退出点,长
if
..then
..else
链可能不是很长可读 - 尤其是在处理某些外部 API 时,这些 API 是为了返回错误代码而不是在失败时抛出异常(例如 Win32 API 或某些 COM 组件),因此您确实需要错误处理代码。如果是这样,在 F# 中执行此操作的具体方法似乎是编写 工作流程。这是我的第一次尝试:
使用示例:
如您所见,它有效地定义了所有构造,一旦遇到
return
,块的其余部分甚至不会被评估。如果块在没有返回的情况下“结束”流动,您将收到运行时异常(到目前为止,我没有看到任何方法可以在编译时强制执行此操作)。这有一些限制。首先,工作流程确实不完整 - 它允许您使用
let
、use
、if
、while
和for
内部,但不是try
..with
或try
..finally
。这是可以完成的 - 你需要实现Block.TryWith
和Block.TryFinally
- 但到目前为止我找不到它们的文档,所以这需要一点一点猜测和更多时间。当我有更多时间时,我可能会回过头来添加它们。其次,由于工作流程实际上只是一系列函数调用和 lambda 的语法糖 - 特别是,所有代码都在 lambda 中 - 您不能在工作流程中使用
let mutable
。这就是我在上面的示例代码中使用ref
和!
的原因,这是通用的解决方法。最后,由于所有 lambda 调用,不可避免地会造成性能损失。据推测,F# 比 C# 更擅长优化此类事情(C# 只保留 IL 中的所有内容),并且可以在 IL 级别内联内容并执行其他技巧;但我对此了解不多,因此确切的性能影响(如果有的话)只能通过分析来确定。
First of all, as others have already noted, it's not "the F# way" (well, not FP way, really). Since you don't deal with statements, but only expressions, there isn't really anything to break out of. In general, this is treated by a nested chain of
if
..then
..else
statements.That said, I can certainly see where there are enough potential exit points that a long
if
..then
..else
chain can be not very readable - especially so when dealing with some external API that's written to return error codes rather than throw exceptions on failures (say Win32 API, or some COM component), so you really need that error handling code. If so, it seems the way to do this in F# in particular would be to write a workflow for it.Here's my first take at it:
Usage sample:
As you can see, it effectively defines all constructs in such a way that, as soon as
return
is encountered, the rest of the block is not even evaluated. If block flows "off the end" without areturn
, you'll get a runtime exception (I don't see any way to enforce this at compile-time so far).This comes with some limitations. First of all, the workflow really isn't complete - it lets you use
let
,use
,if
,while
andfor
inside, but nottry
..with
ortry
..finally
. It can be done - you need to implementBlock.TryWith
andBlock.TryFinally
- but I can't find the docs for them so far, so this will need a little bit of guessing and more time. I might come back to it later when I have more time, and add them.Second, since workflows are really just syntactic sugar for a chain of function calls and lambdas - and, in particular, all your code is in lambdas - you cannot use
let mutable
inside the workflow. It's why I've usedref
and!
in the sample code above, which is the general-purpose workaround.Finally, there's the inevitable performance penalty because of all the lambda calls. Supposedly, F# is better at optimizing such things than, say C# (which just leaves everything as is in IL), and can inline stuff on IL level and do other tricks; but I don't know much about it, so the exact performance hit, if any, could only be determined by profiling.
与 Pavel 类似的选项,但不需要您自己的工作流构建器,只需将代码块放在
seq
表达式中,并让它yield
错误消息。然后在表达式之后,您只需调用 FirstOrDefault 即可获取第一条错误消息(或 null)。由于序列表达式的计算是惰性的,这意味着它只会继续到第一个错误点(假设您从未在序列上调用除
FirstOrDefault
之外的任何内容)。如果没有错误,那么它就会一直运行到最后。因此,如果您这样做,您将能够将收益
视为提前回报。An option similar to Pavel's, but without needing your own workflow builder, is just to put your code block within a
seq
expression, and have ityield
error messages. Then right after the expression, you just callFirstOrDefault
to get the first error message (or null).Since a sequence expression evaluates lazily, that means it'll only proceed to the point of the first error (assuming you never call anything but
FirstOrDefault
on the sequence). And if there's no error then it simply runs through to the end. So if you do it this way you'll be able to think ofyield
just like an early return.这个递归斐波那契函数有两个退出点:
This recursive Fibonacci function has two exit points:
我自己发现了以下方法。基本上,它生成可能退出的
seq
,其中每个退出由yield
生成,然后仅返回 seq 的Seq.head
首先计算的退出值。我不确定我们是否可以在任何程度上将这种方法称为“功能性”。也不确定这种方法的效率如何以及语言惰性是如何在幕后使用的。也不知道这么多年过去了,作者是否还有必要。但这种方式的一些优点是,代码看起来非常像最初使用非函数式语言时的样子,而仅使用最少的内在功能集。请参阅下面问题的第一个示例的代码:
让我们进行一些测试调用:
让我们回顾一下输出:
希望这个想法对那些可能陷入困境的人有所帮助。欢迎对我的上述问题发表任何评论。
I discovered the following approach for myself. Basically, it generates a
seq
of possible exits, where each exit is generated byyield
, then takesSeq.head
of the seq to return only the exit value that is calculated first. I am not sure can we call this approach "functional" to any extent. As well not sure how efficient is this approach and how the language laziness is used under the hood. Also not sure if it is of any need for the author after so many years. But some advantages of this way is that the code looks pretty much like it looked originally in non-functional language while using only a minimal set of intrinsic features only.See below how would look the code of the first sample of the question:
Let's make some test calls:
Let's review the output:
Hope this idea is helpful for those who may got stuck with this. Any comment regarding my concerns above are welcome for this answer.
我喜欢的是,没有人提到 F# 类型结果?
它由 正式给出
这将满足您的需求,并在处理非 IO 错误以提高性能时使用。
一个很好的例子是解析器函数。
如果您不熟悉 |> , 运算符它简单地将左侧(或上面)代码的结果传递给右侧的参数,这里是上面的 lambda 函数
基本情况函数的实现是用户选择的,只要它符合函数类型即可。您可能会注意到,它是任何可能失败的映射(函数)的高度通用函数表示。这包括所有永不失败的功能。
上面的代码是我个人的一些(无代码生成)词法分析器/解析器生成器,仅用于在词法分析器和解析器中构造DFA。代码片段不是我发明的
I like that in no way did any mentioned the F# type Result??
it is formally given by
This will fit your needs and are used when handling none-IO errors for performance boost.
a good example is a parser function
if you are not familiar with the |> operator it simple passe the result of the left side(or above) code to the argument on the right side, here the lambda function above
The implementation of the base case function are of the users choice as long as it conform to the function type. As you might notice is that it is a highly generic function representation of any map (function) which might can fail. This includes all functions that never fails.
The above code are some of my personal (none code generation) lexer/parser generator, only used to construct the DFA in the lexer and the parser. The code snip are not invented by me