F# 懒惰的。。但是F中的数据加载器#

F# 懒惰的。。但是F中的数据加载器#,f#,lazy-loading,eager-loading,agent,mailboxprocessor,F#,Lazy Loading,Eager Loading,Agent,Mailboxprocessor,是否有人知道关于以下主题的“现有技术”: 我有一些数据需要很长时间才能加载。它们是各种股票的历史水平 我想以某种方式预加载它们,以避免使用我的应用程序时出现延迟 然而,在开始时将它们预加载到一个块中会使我的应用程序首先没有响应,这对用户来说是不友好的 所以我不想加载我的数据。。。。除非用户不要求任何,也不玩他已经拥有的东西,在这种情况下,我希望一点一点地得到。因此,它既不是“懒惰”也不是“渴望”,更是“需要时懒惰”和“可以时渴望”,因此缩写为LWYNEWYC 我已经做了以下似乎有效的事情,但

是否有人知道关于以下主题的“现有技术”:

  • 我有一些数据需要很长时间才能加载。它们是各种股票的历史水平
  • 我想以某种方式预加载它们,以避免使用我的应用程序时出现延迟
  • 然而,在开始时将它们预加载到一个块中会使我的应用程序首先没有响应,这对用户来说是不友好的
所以我不想加载我的数据。。。。除非用户不要求任何,也不玩他已经拥有的东西,在这种情况下,我希望一点一点地得到。因此,它既不是“懒惰”也不是“渴望”,更是“需要时懒惰”和“可以时渴望”,因此缩写为LWYNEWYC

我已经做了以下似乎有效的事情,但我只是想知道是否有一个公认的和受祝福的方法来做这件事

let r = LoggingFakeRepo () :> IQuoteRepository
r.getHisto "1" |> ignore  //prints Getting histo for 1 when called

let rc =  RepoCached (r) :> IQuoteRepository
rc.getHisto "1" |> ignore //prints Getting histo for 1 the first time only

let rcc =  RepoCachedEager (r) :> IQuoteRepository
rcc.getHisto "100" |> ignore  //prints Getting histo 1..100 by itself BUT
                              //prints Getting histo 100 immediately when called
课程呢

type IQuoteRepository = 
   abstract getUnderlyings : string seq
   abstract getHisto :  string -> string

type LoggingFakeRepo () =
   interface IQuoteRepository with 
      member x.getUnderlyings = printfn "getting underlyings"
                                [1 .. 100] |> List.map string :> _

      member x.getHisto udl = printfn "getting histo for %A" udl
                              "I am a historical dataset in a disguised party"

type RepoCached (rep : IQuoteRepository) =
   let memoize f =
     let cache = new System.Collections.Generic.Dictionary<_, _>()
     fun x ->
        if cache.ContainsKey(x) then cache.[x]
        else let res = f x
             cache.[x] <- res
             res
   let udls = lazy (rep.getUnderlyings )
   let gethistom = memoize rep.getHisto

   interface IQuoteRepository with 
      member x.getUnderlyings = udls.Force()
      member x.getHisto udl = gethistom udl

type Message = string * AsyncReplyChannel<UnderlyingWrap>
type RepoCachedEager (rep : IQuoteRepository) =
   let udls = rep.getUnderlyings

   let agent = MailboxProcessor<Message>.Start(fun inbox ->
      let repocached = RepoCached (rep) :> IQuoteRepository
      let rec loop l =
         async {  try
                     let timeout = if l|> List.isEmpty  then -1 else 50
                     let! (udl, replyChannel) = inbox.Receive(timeout)
                     replyChannel.Reply(repocached.getHisto udl)
                     do! loop l
                  with 
                  | :? System.TimeoutException -> 
                     let udl::xs = l
                     repocached.getHisto udl |> ignore
                     do! loop xs
          }
      loop (udls |> Seq.toList))

   interface IQuoteRepository with 
      member x.getUnderlyings = udls
      member x.getHisto udl = agent.PostAndReply(fun reply -> udl, reply)
type-ikoterepository=
摘要:字符串序列
抽象getHisto:string->string
类型LoggingFakeRepo()=
与存储库的接口
成员x.getunderyings=printfn“获取参考底图”
[1..100]|>List.map字符串:>_
成员x.getHisto udl=printfn“获取%A的历史记录”udl
“我是伪装派对中的历史数据集”
类型RepoCached(rep:ikoterepository)=
让我们回忆一下f=
let cache=new System.Collections.Generic.Dictionary()
乐趣x->
如果cache.ContainsKey(x)那么cache[x]
否则设res=fx
缓存[x]
让repocached=repocached(rep):>IQuoteRepository
让rec循环l=
异步{try
让timeout=if l |>List.isEmpty然后-1 else 50
let!(udl,replyChannel)=收件箱.接收(超时)
replyChannel.Reply(repocached.gethistoudl)
做!循环l
具有
|:?System.TimeoutException->
让udl::xs=l
repocached.getHisto udl |>忽略
做!循环xs
}
循环(udls |>顺序收费表))
与存储库的接口
成员x.GetUnderlinegs=udls
member x.getHisto udl=agent.PostAndReply(有趣的回复->udl,回复)

我喜欢你的解决方案。我认为使用代理实现一些带有超时的后台加载是一个很好的方法——代理可以很好地封装可变状态,因此它显然是安全的,并且您可以很容易地编码所需的行为

我认为这可能是另一个有用的抽象概念(如果我是正确的,它们现在在FSharpX中可用)。异步序列表示异步生成更多值的计算,因此它们可能是将数据加载程序与代码其余部分分离的好方法

我认为您仍然需要一个代理在某个时刻进行同步,但是您可以使用异步序列很好地分离不同的关注点

加载数据的代码可能如下所示:

let loadStockPrices repo = asyncSeq {
  // TODO: Not sure how you detect that the repository has no more data...
  while true do
    // Get next item from the repository, preferably asynchronously!
    let! data = repo.AsyncGetNextHistoricalValue()
    // Return the value to the caller...
    yield data }
此代码表示数据加载程序,并将其与使用它的代码分离。从使用数据源的代理中,您可以使用
AsyncSeq.iterAsync
来使用这些值并对它们进行处理

使用
iterAsync
,指定为使用者的函数是异步的。它可能会阻塞(即使用
睡眠
),当它阻塞时,源(即加载程序)也会被阻塞。这是一种很好的隐式方法,可以从使用数据的代码控制加载程序


库中还没有(但可能有用)的功能是一个部分急切的求值器,它接受
AsyncSeq
,但尽快从源中获取一定数量的元素并缓存它们(这样消费者在请求值时就不必等待,只要源可以足够快地生成值).

+1我认为您的解决方案已经相当不错了!在回答中添加了一些想法…很有趣,谢谢你的回答。我会考虑一下,并可能建议对fsharpx进行改进。我意识到我们已经习惯了静态数据流和单向执行流。也许这就是为什么经纪人如此流行的原因。我刚刚在scheme中读到了“传播者”,这也打破了这个想法。