简体   繁体   English

如何在 F# 中“去除丑陋”这些嵌套的异步部件

[英]how can I 'de-uglify' these nested async parts, in F#

first, here is the code:首先,这里是代码:

let getCandlesFromAsync (exchange: IExchange) (instrument: Instrument) (interval: TimeSpan) (fromTime: DateTime) (toTime: DateTime) =
    async {
        let rec getAsync (c: CandleData list) (f: DateTime) (t: DateTime) =
            async {
                //info $"requesting {instrument}: {f} - {t}"
                let! candles = exchange.GetCandlesAsync(instrument, interval, f, t)
                if candles.IsError then
                    return (failwith candles.GetError.Describe)
                else
                    //info $"received data {instrument}: {candles.Get.[0].Timestamp} - {candles.Get.[^0].Timestamp}"
                    let c = c @ candles.Get
                    if c.[^0].Timestamp < t - interval then
                        return! getAsync c (c.[^0].Timestamp + interval) t
                    else
                        return c
            }

        let cache = DataCache.getCache instrument
        let candlesFromCache = getCandlesFromCache cache interval fromTime toTime

        let firstTimestamp =
            match candlesFromCache.IsEmpty with
            | true  -> fromTime
            | false -> candlesFromCache.[^0].Timestamp + interval


        // check if we need some new data
        let downloadedCandles =
            async {
                if firstTimestamp < toTime then
                    let! x = getAsync [] firstTimestamp toTime
                    putCandlesInCache cache x
                    return x
                else
                    return []
            }

        let! d = downloadedCandles
        return candlesFromCache @ d
    }

This code is supposed to download price candles from an exchange.此代码应该从交易所下载价格蜡烛。 It has to run at regular interval and catch up with the new data.它必须定期运行并赶上新数据。

Since I need data from a range of timestamps, I try to cache the data that has previously been requested from the exchange.由于我需要来自一系列时间戳的数据,因此我尝试缓存之前从交易所请求的数据。 At the range is always moving forward, I only have to check how much data I already have in the range, and how much I need to get.在范围内总是向前发展,我只需要检查范围内我已经拥有多少数据,以及我需要获得多少。

The code is split into several parts:代码分为几个部分:

  • code that gets data from the cache for a time range (not posted here, but not relevant).从缓存中获取某个时间范围内的数据的代码(此处未发布,但不相关)。 It returns CandleData list .它返回CandleData 列表
  • code that requests data from a time range from the exchange (getAsync), it returns async<CandleData list> .从交换(getAsync)请求时间范围内的数据的代码,它返回async<CandleData list>
  • a small piece of code that determines what is missing and glues the pieces together (the second half of the function).一小段代码,用于确定缺少的内容并将各个部分粘合在一起(函数的后半部分)。

The issue here is that the whole function is expected to be async, but getAsync is recursive, so it has its own async block.这里的问题是整个 function 预计是异步的,但 getAsync 是递归的,所以它有自己的异步块。 Then the code that glues things together has to call getAsync and attach the data to what comes from the cache, so the whole thing is wrapped in an async block as well...然后将事物粘合在一起的代码必须调用 getAsync 并将数据附加到来自缓存的数据,因此整个事物也被包装在一个异步块中......

There has to be a cleaner way to do this, but I'm not sure how.必须有一种更清洁的方法来做到这一点,但我不确定如何。 Any suggestions would be welcome!欢迎大家提出意见!

Separating the functions is the best practice.分离功能是最佳实践。 It doesn't necessarily reduce the number of async s but it makes the code cleaner and easier to understand.它不一定会减少async的数量,但它会使代码更清晰,更易于理解。

The function that downloads from exchange may be on its own:从交易所下载的 function 可能是独立的:

let downloadFromExchange (exchange: IExchange) (instrument: Instrument) (interval: TimeSpan) (f: DateTime) (t: DateTime) =
    let rec getAsync (previousCandles: CandleData list) (f: DateTime) =
        async {
            //info $"requesting {instrument}: {f} - {t}"
            if f < t then return previousCandles else
            let! candles = exchange.GetCandlesAsync(instrument, interval, f, t)
            if candles.IsError then
                return (failwith candles.GetError.Describe)
            else
                //info $"received data {instrument}: {candles.Get.[0].Timestamp} - {candles.Get.[^0].Timestamp}"
                let c = previousCandles @ candles.Get
                match c.[^0].Timestamp + interval with
                | fr when fr < t -> return! getAsync c fr
                | _              -> return  c
        }
    getAsync [] f

I changed the code a little bit to make it clearer for me.我稍微更改了代码以使其更清楚。 I could be wrong but it seems to me the expression c.[^0].Timestamp may result in an exception (or an infinite loop) if the list is empty either in the first call or in a recursive invocation.我可能是错的,但在我看来,如果列表在第一次调用或递归调用中为空,则表达式c.[^0].Timestamp可能会导致异常(或无限循环)。

let getCandlesFromAsync exchange (instrument: Instrument) (interval: TimeSpan) (fromTime: DateTime) (toTime: DateTime) =
    async {
        let cache            = DataCache.getCache instrument
        let candlesFromCache = getCandlesFromCache cache interval fromTime toTime

        let firstTimestamp =
            match candlesFromCache.IsEmpty with
            | true  -> fromTime
            | false -> candlesFromCache.[^0].Timestamp + interval

        let! x = downloadFromExchange exchange instrument interval firstTimestamp toTime
        putCandlesInCache cache x
        return candlesFromCache @ x
    }

I put the condition from < to in the download function, that way the code is cleaner.我将条件from < to下载 function 中,这样代码更干净。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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