簡體   English   中英

F#:在 async{} 塊內正確的 `try` 塊?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM