我正在编写异步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
块中使用的函数的模块,以充分利用计算表达式语法。我想知道我是否做得对,如果我选择了正确的名字,等等。我很少没有正式的函数式编程教育,有时候我甚至不确定我知道自己在做什么。
答案 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 }
当然,如果您需要在代码中的其他位置下载其他用途的数据,则可以将代码拆分为downloadData
和getJson
。
通常,在编写功能代码时,您有两种选择如何组合计算:
在普通F#和异步工作流程中使用语言语法(例如循环,let
和try .. with
,use
。如果您正在编写一些计算,这种方法通常很有效,因为该语言旨在描述计算并且可以做得很好。
使用自定义组合器(例如map
,>>
和|>
或特定于库的运算符)。如果您要对不仅仅是计算的东西进行建模,例如交互式动画,股票期权,用户界面组件或解析器,则需要这样做。但是,如果语言的基本功能不足以表达问题,我只会遵循这条路径。
除此之外,您可能会对F# Data library感兴趣,HTTP requests为各种任务实施帮助,包括JSON parsing,JSON type provider和{{3}},这可能会让您的生活更加轻松。