F#中的“链接”异步函数

时间:2011-07-22 17:27:07

标签: .net asynchronous f#

我在F#中创建了一个函数来从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
        }

现在,我可以通过执行以下操作异步运行此函数:

let test=
    ["MSFT";"YHOO"]
    |>List.map (fun x -> getCSV x (DateTime.Parse("01.01.2000")) (DateTime.Parse("01.01.2010")))
    |> Async.Parallel
    |> Async.RunSynchronously

好的,这很酷。

现在,我想知道的是如何将一些功能应用于价格的历史:

例如:

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函数。

我想知道的是,如果可以在下载仍在进行时开始执行第二个功能:一旦MSFT完成,无需等到YHOO完成计算其返回... < / p>

我知道我可以修改getCSV,但我想知道是否有办法“链接”getReturn函数而无需更改以前编写的模块......

2 个答案:

答案 0 :(得分:10)

我通常会直接在异步工作流中编写对函数的调用。这主要是风格或偏好的问题 - 我认为使用异步工作流编写的代码通常更明确,并且不经常使用高阶函数(尽管它们有时仍然有用):

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来提取数据。然后将整个操作并行化。

或者,您可以使用Joel的解决方案(修改getReturns函数以便它采用异步工作流并返回异步工作流)或定义一个函数Async.map,该函数采用异步工作流并构造一个新的一个将一些函数应用于结果。

使用原始getReturns功能,您可以写:

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 }

答案 1 :(得分:3)

如果您定义了getReturns这样的功能......

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是最后一个参数而不是第一个参数。这允许您部分应用日期参数来生成仅需要执行代码的函数。然后,您可以使用getReturns链接该功能。

let test =
    let getRange = getCSV (DateTime(2000, 1, 1)) (DateTime(2010, 1, 1))
    ["MSFT"; "YHOO"]
    |> List.map (getRange >> getReturns)
    |> Async.Parallel
    |> Async.RunSynchronously

修改

List.nth函数中的所有getReturns次调用都会让我发痒。我宁愿自己使用模式匹配。我认为你可以这样写这个函数:

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 []
}