首页 > 解决方案 > 如何存储方法并稍后为其提供多个参数

问题描述

我正在尝试跟踪值的使用情况,因此我将生成所述值的方法和输入(也被包装)包装到一个名为 Dataslot 的类中。我不知道我将预先包装什么方法和什么值,所以我尝试了各种方法来编写它,并认为我下面的代码可以工作。但let mutable value = funk unpack似乎并没有导致 funk beeing 被识别为一个函数,所以方法 unpack 似乎是错误的方法,我该如何让它工作?

type Dataslot(funk, input:Dataslot[]) as self =

    let mutable reffunk= funk
    let refinput=input
    let unpack= for inpu in refinput do inpu.Value
    let mutable value = funk unpack
    let uses= ResizeArray<Dataslot>[]
    let get1()=
       value
    let mutable get0=fun()->get1()
    let get2()=
       value<-reffunk unpack
       get0<-fun()->get1()
       value
    do for inpu in refinput do inpu.Subscribe(self)
    member x.setfunk(fu)=
        reffunk<-fu
        for u in uses do
            u.Changed
    member x.setinput(index:int, inp:Dataslot)=
        refinput.[index].Unsubscribe(self)
        refinput.[index]=inp
        refinput.[index].Subscribe(self)
        for u in uses do
            u.Changed
    member x.Value
        with get()=get0()
    member x.Changed=get0<-fun()->get2()
    member x.Subscribe(f)=
        uses.Add(f) |>ignore
    member x.Unsubscribe(f)=
        uses.Remove(f) |>ignore

标签: parametersf#

解决方案


我开始回答这个问题,但最后我对你的例子的结构做了一些改变,所以它不再是一个直接的答案——而是另一种解决我想你正在尝试解决的问题的方法。希望这仍然会有所帮助!

我没有使用具体的类Dataslot,而是使用接口,并且还使接口成为通用接口,因此它Dataslot<'T>表示类型的值'T

type Dataslot<'T> = 
  abstract Value : 'T
  abstract Subscribe : (unit -> unit) -> IDisposable

我的订阅机制与工作方式更相似IObservable——你给它一个函数,当值发生变化时应该调用它,它返回一个IDisposable你可以用来取消订阅并停止收到更改通知的函数。

然后,我定义了以下三个可用于处理数据槽的原语(下面的实现):

val mutableSlot   : initial:'T -> ('T -> unit) * Dataslot<'T>
val immutableSlot : value:'T -> Dataslot<'T>
val ( <*> )       : f:Dataslot<('T -> 'R)> -> a:Dataslot<'T> -> Dataslot<'R>
  • immutableSlot创建一个永远不会改变并始终返回初始值的数据槽。
  • mutableSlot创建一个具有初始值的数据槽,并与数据槽一起返回一个设置器。您可以使用 setter 函数来更改数据槽中的值。
  • 运算符获取包含函数的<*>数据槽,包含参数的数据槽并返回带有结果的数据槽 - 只要函数或参数更改,结果就会更改。

值得注意的是<*>operatorn 和immutableSlotfunction 是 Haskellers 称之为applicative functor的一种模式。好消息是,由于部分应用和柯里化的工作原理,您现在也可以使用多参数函数:

let a = immutableSlot 10
let setB, b = mutableSlot 30
let res = immutableSlot (fun a b -> a + b) <*> a <*> b

let sub = res.Subscribe(fun () -> 
  printfn "Result changed to: %d" res.Value )

现在您可以尝试触发更改几次,然后调用Dispose取消订阅通知:

setB 32
setB 30    
sub.Dispose()   
setB 1

这三个操作的实现与您最初的一些代码非常相似。造成这种丑陋的主要原因是跟踪发生更改时需要通知的处理程序。

mutableSlot每当调用 setter 时都需要触发 change 事件:

let mutableSlot initial =
  let mutable value = initial
  let handlers = ResizeArray<_>()
  (fun newValue ->
    value <- newValue
    for h in handlers do h()),
  { new Dataslot<'T> with
    member x.Value = value
    member x.Subscribe h = 
      handlers.Add(h)
      { new IDisposable with 
        member x.Dispose() = handlers.Remove(h) |> ignore } }

immutableSlot更容易,因为它永远不会改变:

let immutableSlot value = 
  { new Dataslot<'T> with
    member x.Value = value
    member x.Subscribe _ = 
      { new IDisposable with member x.Dispose () = () } }

<*>操作符更丑,因为它需要订阅关于它的两个参数的通知。但是,为了避免内存泄漏,它还需要在注册的订阅数达到零时取消订阅(我实际上写了一篇关于此内存泄漏的论文!)

let (<*>) (f:Dataslot<'T -> 'R>) (a:Dataslot<'T>) =
  let mutable value = f.Value a.Value
  let handlers = ResizeArray<_>()
  let update () = 
    value <- f.Value a.Value
    for h in handlers do h()
  let mutable fsub = { new IDisposable with member x.Dispose() = () }
  let mutable asub = { new IDisposable with member x.Dispose() = () }
  { new Dataslot<'R> with
    member x.Value = 
      if handlers.Count > 0 then value else f.Value a.Value
    member x.Subscribe h = 
      handlers.Add(h)
      if handlers.Count = 1 then 
        fsub <- f.Subscribe(update)
        asub <- a.Subscribe(update)
        value <- f.Value a.Value
      { new IDisposable with 
        member x.Dispose() = 
          handlers.Remove(h) |> ignore 
          if handlers.Count = 0 then
            fsub.Dispose()
            asub.Dispose() } }

编辑:有一个非常棘手的方面<*>是它什么时候重新计算它的值。如果有人订阅了更改通知,我们假设他们需要该值,因此每次参数之一更改时(急切地)我们都会重新计算它。当没有人订阅时,我们假设他们可能不会访问该值,因此我们只会在Value访问时懒惰地重新计算。我们可以只订阅并且从不取消订阅(并且总是急切地更新),但是如果您反复订阅和取消订阅,这可能会导致内存泄漏。


推荐阅读