简体   繁体   English

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

[英]“Chaining” asynchronous functions in F#

I have created a function in F# to recover historical data from Yahoo (the classic asynchronous example for 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
        }

Now, I can run this function asynchronously by doing the following:现在,我可以通过执行以下操作异步运行此 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

Ok that's cool.好,这很酷。

Now, what I would like to know is how to apply some function to this which is the history of prices:现在,我想知道的是如何将一些 function 应用于价格历史:

For example:例如:

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))

So the trivial way of doing it is:所以这样做的简单方法是:

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;;

However, the getReturns function is executed once every file is downloaded and parsed.但是,一旦下载并解析了每个文件,就会执行getReturns function。

What I would like to know, is if it is possible to start execution the second function while the downloads are still taking place: once MSFT is done, no need to wait until YHOO is done to compute its return...我想知道的是,是否有可能在下载仍在进行时开始执行第二个 function:一旦 MSFT 完成,无需等到 YHOO 完成计算其返回...

I know that I could modify getCSV but I would like to know if there is a way to "chain" the getReturn function without having to change a previously written module...我知道我可以修改getCSV但我想知道是否有办法“链接” getReturn function 而无需更改以前编写的模块......

I would typically write the call to the function directly inside an asynchronous workflow.我通常会直接在异步工作流中编写对 function 的调用。 This is mostly a matter of style or preference - I think that code written using asynchronous workflows is generally more explicit and doesn't use higher-order functions as often (though they're still sometimes useful):这主要是风格或偏好的问题 - 我认为使用异步工作流编写的代码通常更明确,并且不经常使用高阶函数(尽管它们有时仍然有用):

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 

This means that the workflows executed in parallel first get the data and then call getRteurns to extract the data.这意味着并行执行的工作流首先获取数据,然后调用getRteurns来提取数据。 The entire operation is then parallelized.然后将整个操作并行化。

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. 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 .

Using your original getReturns function, you can then write:使用您原来的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

The definition of Async.map is quite simple: Async.map的定义很简单:

module Async =
  let map f workflow = async {
    let! res = workflow
    return f res }

If you defined your getReturns function like this...如果您像这样定义了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)))))
}

Then you would be able to do this:然后你就可以做到这一点:

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

You could clean it up further by changing getCSV so that ticker is the last parameter instead of the first.您可以通过更改getCSV进一步清理它,以便ticker是最后一个参数而不是第一个参数。 This allows you to partially apply the date arguments to produce a function that only requires a ticker to execute.这允许您部分应用日期 arguments 以生成仅需要执行代码的 function。 Then you can chain that function with getReturns .然后,您可以将 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

Edit :编辑

All those List.nth calls in your getReturns function make me itchy.您的getReturns function 中的所有List.nth调用让我发痒。 I'd rather use pattern-matching myself.我宁愿自己使用模式匹配。 I think you could write that function like this instead:我想你可以这样写 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM