2

我正在编写一个异步 HTTP API 客户端模块/库。为了使一切尽可能 DRY,我试图从进行 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块中使用的功能的模块,以利用计算表达式语法的所有优势。我想知道我是否做得对,是否选择了正确的名字,等等。我几乎没有接受过正规的函数式编程教育,有时我什至不确定我是否知道自己在做什么。

4

1 回答 1

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# 数据库感兴趣,它为各种任务实现了帮助程序,包括HTTP 请求JSON 解析JSON 类型提供程序,这可能会让您的生活更加轻松。

于 2013-01-21T15:17:04.947 回答