[英]F#: Proper `try` blocks inside an async{} block?
為了簡化我的場景,假設我有這個簡單的代碼:
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 =
let! foo =
bit 因為它等待異步工作負載。
但是,如果您想更改 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"
}
但是,我對這個解決方案並不是 100% 滿意,因為 try-with 太寬了,因為它還涵蓋了對 DoSomethingWithFoo 函數的調用,我想把它留在 try-with 塊之外。 有什么更好的方法可以在不編寫非慣用 F# 的情況下解決這個問題? 我應該在 Microsoft 的 F# GitHub 存儲庫中將編譯器錯誤報告為功能請求嗎?
您可以在包含try...with
的新async
中包裝對SomeFuncThatThrows
的調用:
let SomeWrapper () =
async {
let! foo =
async {
try
return! SomeFuncThatThrows()
with
| :? InvalidOperationException ->
Console.Error.WriteLine "aborted"
Environment.Exit 1
return failwith "unreachable"
}
DoSomethingWithFoo foo
}
@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
分支。
有什么更好的方法可以在不編寫非慣用 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 ())
0
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.