Sven Heinz
Sven Heinz

Reputation: 51

How to store a method and later feed it multiple parameters

I am trying to track the usage of values, so I wrap the method generating said value and the inputs(which are also wrapped) into a class I call Dataslot. I do not know what method and what value I will wrap beforehand, so I tried various ways to write this and thought my code below would work. But let mutable value = funk unpack does not seem to result in funk beeing recognized as a function, so the method unpack seems to be the wrong approach, how do i get this to work?

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

Upvotes: 1

Views: 104

Answers (1)

Tomas Petricek
Tomas Petricek

Reputation: 243106

I started answering the question, but I ended up doing a couple of changes to the strucutre of your example, so it is no longer a direct answer - but rather, another approach to solve the problem that I imagine you are trying to solve. Hopefully, this will still help though!

Rather than using a concrete class for Dataslot, I'm using an interface and I also made the interface generic, so that Dataslot<'T> represents a value of type 'T:

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

My subscription mechanims is more similar to how IObservable works - you give it a function that should be called whenever a value changes and it returns an IDisposable that you can use to cancel the subscription and stop being notified about changes.

I then defined the following three primitives that you can use to work with data slots (implementation below):

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 creates a data slot that never changes and always returns the initial value.
  • mutableSlot creates a data slot with an initial value and returns a setter together with data slot. You can use the setter function to change the value in the data slot.
  • The <*> operator takes a data slot containing a function, data slot containing an argument and returns a data slot with the result - the results changes whenever the function or the argument change.

It is worth noting that the <*> operatorn and the immutableSlot function are a pattern that Haskellers call applicative functor. The nice thing is that thanks to how partial application and currying works, you can now use multiple-argument functions too:

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 )

Now you can try triggering the change a few times and then calling Dispose to unsubscribe from notifications:

setB 32
setB 30    
sub.Dispose()   
setB 1

The implementation of the three operations is quite similar to some of the code you had originally. The main thing that makes this ugly is keeping track of the handlers that need to be notified when a change happens.

mutableSlot needs to trigger change event whenever the setter is called:

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 is easier, because it never chnages:

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

The <*> operator is uglier, because it needs to subscribe to notifications on its two arguments. However, to avoid memory leaks, it also needs to unsubscribe when the number of subscriptions registered with it reaches zero (I actually 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() } }

EDIT: There is quite tricky aspect in the implementation of <*> which is when does it re-compute its value. If someone subscribes for change notifications, we assume they'll need the value and so we recompute it each time one of the arguments changes (eagerly). When nobody subscribes, we assume they might not access the value so we only recompute lazily when Value is accessed. We could just subscribe and never un-subscribe (and update always eagerly), but this could lead to memory leaks if you repeatedly subscribe and unsubscribe.

Upvotes: 5

Related Questions