Exception handling 如何在F#任务计算表达式中的每个步骤上实现非嵌套异常处理?

Exception handling 如何在F#任务计算表达式中的每个步骤上实现非嵌套异常处理?,exception-handling,f#,computation-expression,Exception Handling,F#,Computation Expression,考虑到F#,我可以写:- task { try let! accessToken = getAccessTokenAsync a b try let! resource = getResourceAsync accessToken uri // do stuff with | ex -> printfn "Failed to get API resource.

考虑到F#,我可以写:-

task {
    try
        let! accessToken = getAccessTokenAsync a b

        try
            let! resource = getResourceAsync accessToken uri
            // do stuff
        with
            | ex -> printfn "Failed to get API resource.  %s" ex.Message
    with
        | ex -> printfn "Failed to get access token.  %s" ex.Message

    return ()
}
但是我想做的是围绕两个
getBlahAsync
函数调用进行非嵌套异常处理。这可以在C#中通过具有多个
wait
s的
async
方法轻松完成

如何在F#计算表达式中实现这一点?如果我以简单明了的方式进行尝试,第一个
try..with
accessToken
不会流入第二个
try..with

(嵌套的问题是,
//do stuff
部分可能会增长一点,从而将外部的
推离
try
越来越远)

如何在C#中执行此操作:-


在编辑之后,我发现您真正想要的是“提前返回”——一种在到达终点之前“中断”执行流的能力。这在F#中通常是不可能的(尽管一些计算生成器可能为此提供专门的工具),因为F#基本上是基于表达式的,而不是基于语句的

缺乏早期回报是一件好事,因为它迫使你仔细思考你的程序应该做什么,而不仅仅是保释。但这是另一个时代的哲学讨论

然而,还有其他方法可以达到类似的效果。在这种特定情况下,我会将这两个操作及其异常处理放在单独的函数中,然后将这些函数链接在一起:

task {
    let token = task {
        try
            let! t = getAccessTokenAsync a b
            return Some t
        with
            | ex -> printfn "Failed to get access token.  %s" ex.Message
                    return None
    }

    let resouce t = task {
        try 
            let! r = getResourceAsync accessToken uri
            // do stuff
        with 
            | ex -> printfn "Failed to get API resource.  %s" ex.Message
    }

    let! t = token
    match t with
       | None -> return ()
       | Some token -> do! resource token
}
如果您发现自己经常面临类似的问题,您可能希望投资一些辅助函数,这些函数包装异常处理和
选项
链接:

// Applies given Task-returning function to the given Option value,
// if the Option value is None, returns None again.
// This is essentially Option.map wrapped in a task.
let (<*>) f x = task {
    match x with
    | None -> return None
    | Some r -> let! r' = f r
                return Some r'
}

// Executes given Option-returning task, returns None if an exception was thrown.
let try'' errMsg f = task {
    try return! f
    with ex -> 
        printfn "%s %s" errMsg ex.Message
        return None
}

// Executes given task, returns its result wrapped in Some,
// or returns None if an exception was thrown.
let try' errMsg f = try'' errMsg <| task { let! r = f
                                           return Some r }


task {
    let! token = getAccessTokenAsync a b |> try' "Failed to get access token."
    let! resource = getResourceAsync uri <*> token |> try'' "Failed to get API resource."
    do! doStuff <*> resource
}
//将给定的任务返回函数应用于给定的选项值,
//如果选项值为“无”,则再次返回“无”。
//这本质上是包装在任务中的Option.map。
设()fx=task{
将x与
|无->返回无
|一些r->让!r'=fr
返回一些r'
}
//执行给定的返回任务选项,如果引发异常,则返回None。
让我们试试“”errMsg f=task{
尝试返回
使用ex->
打印fn“%s%s”错误消息
一无所获
}
//执行给定的任务,返回封装在某些,
//如果引发异常,则返回None。
让try“errMsg f=try”“errMsg try”“无法获取访问令牌。”
让我来!resource=getResourceAsync uri令牌|>尝试“”无法获取API资源
做凝灰岩资源
}

这说明了处理异常的首选F#方法:避免它们,从不抛出它们,而是返回错误类型(上面的示例使用,但也请参见示例),如果您必须与抛出异常的库代码交互,将它们放入将异常转换为错误类型的包装器中。

翻译C代码的主要问题是F不允许您使用
return
提前跳出函数体。您可以通过各种方式避免嵌套异常,但不能提前返回。这可以实现为,但这更像是一种好奇,而不是您实际想要在这里使用的东西

我的建议是将函数拆分为一个获取所有资源并处理异常的函数和另一个处理异常的函数。这并不能消除嵌套,但会使代码具有相当的可读性

let doStuff accessToken resource = task {
  // do stuff
}

let getResourcesAndDoStuff a b uri = task {
  try
    let! accessToken = getAccessTokenAsync a b
    try
      let! resource = getResourceAsync accessToken uri
      return! doStuff accessToken resource
    with ex -> 
      printfn "Failed to get API resource.  %s" ex.Message
  with ex ->
    printfn "Failed to get access token.  %s" ex.Message 
}

顺便问一下,您使用
task
而不是普通的内置F#
async
工作流有什么特别的原因吗?这不一定是个问题,但
async
组合更好,支持取消,因此,这通常是一个明智的默认选择。

你能展示一下你在C#中是如何做到的吗?@FyodorSoikin-原始帖子,编辑了我在C#中是如何做到的。我以前使用过
async
,但没有任何问题-用于与我现在使用的
HttpClient
调用完全相同的调用(当然我的示例中没有显示)。但是我最近通过Giraffe框架读到了这个新的
任务
CE,以及它如何更适合处理.NET任务。所以我想让它转一转。我非常喜欢F#:记录类型的域建模、DU、穷举模式匹配等等。但是F#基本上是基于表达式的,缺乏早期回报,无法表达:做这个语句,然后那个语句(垂直排列,如C#示例中的)每次都会让我丧命。我的完美语言将拥有所有这些美妙的FP构造,比如DU和模式匹配,以及语句序列和早期返回。到目前为止,我遇到的唯一能勾选所有这些框的语言是锈迹。感谢这一点:这里有很多好的想法和思想食粮。
这说明了处理异常的首选方法:避免它们,永远不要抛出它们。
-在这一点上似乎存在一些分歧。e、 格林似乎更模棱两可。
let doStuff accessToken resource = task {
  // do stuff
}

let getResourcesAndDoStuff a b uri = task {
  try
    let! accessToken = getAccessTokenAsync a b
    try
      let! resource = getResourceAsync accessToken uri
      return! doStuff accessToken resource
    with ex -> 
      printfn "Failed to get API resource.  %s" ex.Message
  with ex ->
    printfn "Failed to get access token.  %s" ex.Message 
}