F #: Правильные блоки `try` внутри блока async {}? - PullRequest
2 голосов
/ 24 января 2020

Чтобы упростить мой сценарий, давайте предположим, что у меня есть этот простой код:

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. Чтобы программа не взломала sh в этом сценарии, я хочу обработать исключение. Тогда для F # newb ie легко изменить 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 =, поскольку он ожидает рабочей нагрузки asyn c.

Однако, если вы хотите изменить 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. Есть ли лучший способ исправить это без написания non-idiomati c F #? Должен ли я сообщать об ошибке компилятора как запрос функции в репозитории Microsoft F # GitHub?

Ответы [ 3 ]

2 голосов
/ 24 января 2020

Вы можете заключить вызов в SomeFuncThatThrows в новый async, который содержит try...with:

let SomeWrapper () =
    async {
        let! foo = 
            async {
                try
                    return! SomeFuncThatThrows()
                with
                | :? InvalidOperationException ->
                    Console.Error.WriteLine "aborted"
                    Environment.Exit 1
                    return failwith "unreachable"
            }
        DoSomethingWithFoo foo
    }
1 голос
/ 25 января 2020

Ответ от @nilekirk работает и кодирует непосредственно логи c, которые вы искали, но, как вы заметили в комментариях, это довольно сложная структура syntacti c - вам нужно вложенное выражение 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 филиал.

0 голосов
/ 04 февраля 2020

Любой лучший способ исправить это без написания не-идиоматических c F #?

В идиоматических c 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
...