为了简化我的场景,让我们假设我有以下简单的代码:
let someCondition = false
let SomeFuncThatThrows () =
async {
if someCondition then
raise <| InvalidOperationException()
return 0
}
let DoSomethingWithFoo (foo: int) =
Console.WriteLine (foo.ToString())
let SomeWrapper () =
async {
let! foo = SomeFuncThatThrows()
DoSomethingWithFoo foo
}
[<EntryPoint>]
let main argv =
Async.RunSynchronously (SomeWrapper ())
0在执行它时,它显然只是打印"0“。然而,总有一天,环境发生了变化,一些外部因素使someCondition成为true。为了防止程序在这种情况下崩溃,我想要处理异常。然后,对于一个F#新手来说,很容易改变SomeWrapper,添加一个try-with块,大多数人会认为这样做是可行的:
let SomeWrapper () =
async {
let! foo =
try
SomeFuncThatThrows()
with
| :? InvalidOperationException ->
Console.Error.WriteLine "aborted"
Environment.Exit 1
failwith "unreachable"
DoSomethingWithFoo foo
}但是,上面的方法不起作用(异常仍未处理),因为SomeFuncThatThrows返回一个成功的结果:一个Async<int>元素。抛出异常的是let! foo =位,因为它等待异步工作负载。
但是,如果您希望更改SomeWrapper以修复异常处理,许多人可能认为这是可能的:
let SomeWrapper () =
async {
let foo =
try
let! fooAux = SomeFuncThatThrows()
fooAux
with
| :? InvalidOperationException ->
Console.Error.WriteLine "aborted"
Environment.Exit 1
failwith "unreachable"
DoSomethingWithFoo foo
}但是不,编译器不高兴,因为它表明了以下错误:
/./Program.fs(17,17):错误FS0750:此构造只能在计算表达式(FS0750) (SomeProject)中使用
那么,这似乎是我唯一能解决的办法就是这样:
let SomeWrapper () =
async {
try
let! foo = SomeFuncThatThrows()
DoSomethingWithFoo foo
with
| :? InvalidOperationException ->
Console.Error.WriteLine "aborted"
Environment.Exit 1
failwith "unreachable"
}但是,我对这个解决方案并不百分之百满意,因为try-with太宽了,因为它还包含了对DoSomethingWithFoo函数的调用,我想把它放在try-with块之外。有什么更好的方法可以在不编写非惯用F#的情况下修复这个问题?我是否应该将编译器错误报告为微软的F# GitHub repo中的特性请求?
发布于 2020-01-24 15:48:31
可以将对SomeFuncThatThrows的调用包装在包含try...with的新async中。
let SomeWrapper () =
async {
let! foo =
async {
try
return! SomeFuncThatThrows()
with
| :? InvalidOperationException ->
Console.Error.WriteLine "aborted"
Environment.Exit 1
return failwith "unreachable"
}
DoSomethingWithFoo foo
}发布于 2020-01-24 21:15:33
@nilekirk的回答有效并直接编码了您所寻找的逻辑,但是正如您在注释中所指出的,它是一个相当复杂的语法结构--您需要一个嵌套的async { .. }表达式。
您可以将嵌套的async块提取到一个单独的函数中,从而使代码更加可读性:
let SafeSomeFunc () = async {
try
return! SomeFuncThatThrows()
with
| :? InvalidOperationException ->
Console.Error.WriteLine "aborted"
Environment.Exit 1
return failwith "unreachable"
}
let SomeWrapper2 () = async {
let! foo = SafeSomeFunc ()
DoSomethingWithFoo foo
}在这里,我们实际上需要将一些返回值放到with分支中。
发布于 2020-02-04 04:56:24
有更好的方法来解决这个问题而不用编写非惯用的F#吗?
在惯用的F#和函数式代码中,我们尽量避免使用异常和副作用。
Environment.Exit是一个很大的副作用,不要用它.
如果SomeFuncThatThrows()必须能够抛出异常(例如,您不能修改其源代码)。然后尝试将其封装到一个安全函数中,该函数返回一个Option值,然后使用该函数。
可以将整个代码重写为:
let someCondition = true
let SomeFuncThatThrows () =
async {
if someCondition then
raise <| InvalidOperationException()
return 0
}
let SomeFunc () =
async {
try
let! foo = SomeFuncThatThrows()
return Some foo
with _ ->
return None
}
let DoSomethingWithFoo (foo: int) =
Console.WriteLine (foo.ToString())
let SomeWrapper () =
async {
match! SomeFunc() with
| Some foo -> DoSomethingWithFoo foo
| None -> Console.Error.WriteLine "aborted"
}
[<EntryPoint>]
let main argv =
Async.RunSynchronously (SomeWrapper ())
0https://stackoverflow.com/questions/59894680
复制相似问题