我试图跟踪值的使用,因此将生成所述值和输入(也被包装)的方法包装到一个称为Dataslot的类中。
我不知道我会预先包装什么方法和什么值,所以我尝试了多种方式编写此代码,并认为下面的代码可以工作。
但是let mutable value = funk unpack
似乎并没有导致将funk beeing识别为一个函数,因此解压缩方法似乎是错误的方法,我该如何使用它?
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
答案 0 :(得分:5)
我开始回答这个问题,但是我最终对您的示例的结构进行了一些更改,因此它不再是直接的答案-而是解决我认为您正在尝试解决的问题的另一种方法解决。希望这仍然会有所帮助!
我没有使用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和数据插槽。您可以使用设置器功能更改数据插槽中的值。<*>
运算符获取一个包含函数的数据插槽,包含一个参数的数据插槽,并返回带有结果的数据插槽-每当函数或参数更改时,结果都会更改。值得注意的是,<*>
运算符和immutableSlot
函数是Haskellers称为applicative functor的模式。令人高兴的是,由于部分应用程序和currying的工作原理,您现在还可以使用多参数函数:
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
每当调用设置器时都需要触发更改事件:
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 () = () } }
<*>
运算符比较丑陋,因为它需要订阅有关其两个参数的通知。但是,为避免内存泄漏,当向其注册的订阅数达到零(我实际上是wrote a paper about this memory leak!)时,还需要取消订阅
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
时才进行延迟计算。我们可以只订阅而永不退订(并且总是热切地更新),但是如果您反复订阅和退订,则可能导致内存泄漏。