我试图跟踪值的用法,因此将生成所述值和输入(也被包装)的方法包装到一个称为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
Run Code Online (Sandbox Code Playgroud)
我开始回答这个问题,但是最后我对您的示例结构进行了一些更改,因此它不再是直接的答案-而是解决我认为您正在尝试解决的问题的另一种方法。希望这仍然会有所帮助!
Dataslot我没有使用具体的类,而是使用了一个接口,并且使该接口通用,从而Dataslot<'T>表示了type的值'T:
type Dataslot<'T> =
abstract Value : 'T
abstract Subscribe : (unit -> unit) -> IDisposable
Run Code Online (Sandbox Code Playgroud)
我的订阅机制与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>
Run Code Online (Sandbox Code Playgroud)
immutableSlot 创建一个永不更改并始终返回初始值的数据插槽。mutableSlot创建一个具有初始值的数据插槽,然后将设置器与数据插槽一起返回。您可以使用设置器功能更改数据插槽中的值。<*>运算符采用包含含有自变量的函数,数据时隙中的数据时隙,并返回结果的数据时隙-的结果改变每当函数或自变量的变化。值得注意的是,<*>运算符和immutableSlot函数是Haskellers称为应用函子的模式。令人高兴的是,由于部分应用程序和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 )
Run Code Online (Sandbox Code Playgroud)
现在,您可以尝试触发更改几次,然后调用Dispose取消订阅通知:
setB 32
setB 30
sub.Dispose()
setB 1
Run Code Online (Sandbox Code Playgroud)
这三个操作的实现与您最初编写的某些代码非常相似。造成此丑陋的主要原因是跟踪发生更改时需要通知的处理程序。
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 } }
Run Code Online (Sandbox Code Playgroud)
immutableSlot 更容易,因为它永远不会改变:
let immutableSlot value =
{ new Dataslot<'T> with
member x.Value = value
member x.Subscribe _ =
{ new IDisposable with member x.Dispose () = () } }
Run Code Online (Sandbox Code Playgroud)
该<*>运营商是难看,因为它需要订阅它的两个参数的通知。但是,为避免内存泄漏,当向其注册的订阅数达到零时,它也需要取消订阅(我实际上写了一篇有关此内存泄漏的论文!)
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() } }
Run Code Online (Sandbox Code Playgroud)
编辑:在实现中有一个非常棘手的方面<*>是何时重新计算其值。如果有人订阅变更通知,我们假设他们将需要该值,因此每次(其中一个参数)更改时,我们都会重新计算该值。当没有人订阅时,我们假设他们可能不会访问该值,因此我们仅在Value访问时才延迟重新计算。我们可以只订阅而永不退订(并且总是急于更新),但是如果您反复订阅和退订,这可能会导致内存泄漏。