簡體   English   中英

F# 中的“鏈接”異步函數

[英]“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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM