[英]“Chaining” asynchronous functions in F#
我在 F# 中创建了一个 function 来从 Yahoo(F# 的经典异步示例)恢复历史数据:
let getCSV ticker dStart dEnd =
async {
let query = getFileUrl ticker dStart dEnd
let req = WebRequest.Create(query)
use! resp = req.AsyncGetResponse()
use stream= resp.GetResponseStream()
use reader = new StreamReader(stream)
let content = reader.ReadToEnd()
let ts = parseData content
return ts
}
现在,我可以通过执行以下操作异步运行此 function:
let test=
["MSFT";"YHOO"]
|>List.map (fun x -> getCSV x (DateTime.Parse("01.01.2000")) (DateTime.Parse("01.01.2010")))
|> Async.Parallel
|> Async.RunSynchronously
好,这很酷。
现在,我想知道的是如何将一些 function 应用于价格历史:
例如:
let getReturns (prices:(DateTime *float)list) =
[for i in 1..(prices.Length-1) -> i]
|> List.map (fun i ->(fst (List.nth prices i), (snd (List.nth prices i))/(snd (List.nth prices (i-1) )) - 1.0))
所以这样做的简单方法是:
let test2=
["MSFT";"YHOO"]
|>List.map (fun x -> getCSV x (DateTime.Parse("01.01.2000")) (DateTime.Parse("01.01.2010")))
|> Async.Parallel
|> Async.RunSynchronously
|> Array.map getReturns;;
但是,一旦下载并解析了每个文件,就会执行getReturns
function。
我想知道的是,是否有可能在下载仍在进行时开始执行第二个 function:一旦 MSFT 完成,无需等到 YHOO 完成计算其返回...
我知道我可以修改getCSV
但我想知道是否有办法“链接” getReturn
function 而无需更改以前编写的模块......
我通常会直接在异步工作流中编写对 function 的调用。 这主要是风格或偏好的问题 - 我认为使用异步工作流编写的代码通常更明确,并且不经常使用高阶函数(尽管它们有时仍然有用):
let test=
[ for stock in ["MSFT";"YHOO"] ->
async { let! data = getCSV stock (DateTime(2000, 1, 1)) (DateTime(2010, 1, 1))
return getReturns data } ]
|> Async.Parallel
|> Async.RunSynchronously
这意味着并行执行的工作流首先获取数据,然后调用getRteurns
来提取数据。 然后将整个操作并行化。
Alternatively, you could either use Joel's solution (modify the getReturns
function so that it takes an asynchronous workflow and returns an asynchronous workflow) or define a function Async.map
that takes an asynchronous workflow and constructs a new one that applies some function to the result .
使用您原来的getReturns
function,您可以编写:
let test=
["MSFT";"YHOO"]
// For every stock name, generate an asynchronous workflow
|> List.map (fun x -> getCSV x (DateTime(2000, 1, 1)) (DateTime(2010, 1, 1)))
// For every workflow, transform it into a workflow that
// applies 'getReturns' to the result of the original workflow
|> List.map (Async.map getReturns)
// Run them all in parallel
|> Async.Parallel
|> Async.RunSynchronously
Async.map
的定义很简单:
module Async =
let map f workflow = async {
let! res = workflow
return f res }
如果您像这样定义了getReturns
function ...
let getReturns (prices:Async<(DateTime * float) list>) = async {
let! prices = prices
return [for i in 1..(prices.Length-1) -> i]
|> List.map (fun i ->(fst (List.nth prices i), (snd (List.nth prices i))/(snd (List.nth prices (i-1)))))
}
然后你就可以做到这一点:
let test=
["MSFT";"YHOO"]
|> List.map (fun x -> getCSV x (DateTime(2000, 1, 1)) (DateTime(2010, 1, 1)))
|> List.map getReturns
|> Async.Parallel
|> Async.RunSynchronously
您可以通过更改getCSV
进一步清理它,以便ticker
是最后一个参数而不是第一个参数。 这允许您部分应用日期 arguments 以生成仅需要执行代码的 function。 然后,您可以将 function 与getReturns
。
let test =
let getRange = getCSV (DateTime(2000, 1, 1)) (DateTime(2010, 1, 1))
["MSFT"; "YHOO"]
|> List.map (getRange >> getReturns)
|> Async.Parallel
|> Async.RunSynchronously
编辑:
您的getReturns
function 中的所有List.nth
调用让我发痒。 我宁愿自己使用模式匹配。 我想你可以这样写 function :
let getReturns2 (prices: Async<(DateTime * float) list>) = async {
let! prices = prices
let rec loop items output =
match items with
| (_, last) :: (time, current) :: rest ->
loop rest ((time, (last / current)) :: output)
| [ item ] ->
List.rev (item :: output)
| [] ->
List.rev output
return loop prices []
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.