在F#中编写异步计算

时间:2013-01-21 15:01:37

标签: .net asynchronous f# functional-programming computation-expression

我正在编写异步HTTP API客户端模块/库。为了使所有内容尽可能干,我试图从进行API调用的单独部分组成每个HTTP API调用,自下而上:构建请求,获取响应,将响应读入字符串缓冲区,解析JSON内容将字符串缓冲区转换为对象。

到目前为止,我有这段代码:

module ApiUtils =
    // ... request builder fns omitted ...

    let getResponse<'a> (request : Net.WebRequest) = 
        request.AsyncGetResponse()

    let readResponse (response : Net.WebResponse) =
        use reader = new StreamReader(response.GetResponseStream())
        reader.AsyncReadToEnd()

    let getString = getResponse >> (Async.flatMap readResponse)

    let parseJson<'T> responseText : 'T =
        Json.JsonConvert.DeserializeObject<'T> responseText

    let getJson<'T> = getString >> (Async.map parseJson<'T>)

而且,正如您所看到的,我已经使用我自己的附加功能扩展了Async模块:

module Async =
    let map f m =
        async {
            let! v = m
            return f v
        }

    let flatMap f m =
        async {
            let! v = m
            return! f v
        }

我想要实现的目标是构建一个具有我可以在async块中使用的函数的模块,以充分利用计算表达式语法。我想知道我是否做得对,如果我选择了正确的名字,等等。我很少没有正式的函数式编程教育,有时候我甚至不确定我知道自己在做什么。

1 个答案:

答案 0 :(得分:6)

在F#中编写异步代码时,我发现使用内置计算异步工作流语法更容易,而不是尝试构建更复杂的组合器。

在您的示例中,如果您只编写一个简单的函数,则不会真正复制任何代码,因此我认为以下内容不会破坏DRY原则并且它相当简单(并且也很容易扩展代码来处理异常,否则会很困难):

let getJson<'T> (request:Net.WebRequest) = async { 
  let! response = request.AsyncGetResponse()
  use reader = new StreamReader(response.GetResponseStream())
  let! data = reader.AsyncReadToEnd()
  return Json.JsonConvert.DeserializeObject<'T> data }

当然,如果您需要在代码中的其他位置下载其他用途的数据,则可以将代码拆分为downloadDatagetJson

通常,在编写功能代码时,您有两种选择如何组合计算:

  • 在普通F#和异步工作流程中使用语言语法(例如循环,lettry .. withuse。如果您正在编写一些计算,这种方法通常很有效,因为该语言旨在描述计算并且可以做得很好。

  • 使用自定义组合器(例如map>>|>或特定于库的运算符)。如果您要对不仅仅是计算的东西进行建模,例如交互式动画,股票期权,用户界面组件或解析器,则需要这样做。但是,如果语言的基本功能不足以表达问题,我只会遵循这条路径。

除此之外,您可能会对F# Data library感兴趣,HTTP requests为各种任务实施帮助,包括JSON parsingJSON type provider和{{3}},这可能会让您的生活更加轻松。