Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# - Set Property if changed

Tags:

f#

f#-3.0

I have a class with properties that have several side effects that fire when its setters are called, such as change events fired and/or rendering actions. How could I design a function that would set the property only if its value needs to change?

With refs, I would just do something like this:

let setIfChanged (oldVal: Ref<'T>) (newVal: 'T) = if newVal <> !oldVal then oldVal := newVal 
let y = ref 0
setIfChanged y 1

However, mutable properties aren't refs, and I can't seem to find any way to design such a thing that will compile. For example,

let setIfChanged oldVal newVal = if newVal <> oldVal then oldVal <- newVal 

will just tell me that oldVal is not mutable.

Is there a way to annotate oldVal so that it is for mutable (settable) properties? Or is there a better way?

like image 257
TheDarkSaint Avatar asked May 26 '15 22:05

TheDarkSaint


2 Answers

I don't think there is any easy nice way to do this "for free".

One trick that might make this a bit easier is that you can actually treat the getter and setter of the property as functions, so you can write setIfChanged as a function taking two functions:

let setIfChanged getter setter v = 
  if getter() <> v then setter(v)

Given a class A with proeperty P, you can then call this using set_P and get_P as parameters:

let a = A()
setIfChanged a.get_P a.set_P 0
setIfChanged a.get_P a.set_P 1

The fact that the compiler allows you to access get_P and set_P is a little known trick - so it might be a bit confusing for many people. For completeness, here is the definition of A that I used for testing:

type A() =
  let mutable p = 0
  member x.P 
    with get() = p
    and set(v) = printfn "Setting!"; p <- v

A more sophisticated approach would be to use quotations and reflection, which would be slower, but it would let you write something like setIfChanged <@ a.P @> 42.

like image 89
Tomas Petricek Avatar answered Oct 16 '22 23:10

Tomas Petricek


You can do:

let setIfChanged (oldVal: 'a byref) (newVal : 'a) = if newVal <> oldVal then oldVal <- newVal
let mutable test = 42
setIfChanged &test 24

That being said, given your explanation of goals, I would recommend looking at Gjallarhorn. It provides support for signaling on top of mutable values, and is designed to be extremely flexible in terms of what happens when a value changes. It could be used as an underlying mechanism upon which you could easily build your signals when things change (if it doesn't already provide what you need via the View module).


Edit:

Given your comment that the property in question lies in a third party assembly, one option would be to use the dynamic support in F# to override the op_DynamicAssignment operator (?<-) to dynamically dispatch to the method, but raise your "notification" in the process.

Given:

let (?<-) source property (value : 'a) = 
    let p = source.GetType().GetProperty(property)
    let r = p.GetValue(source, null) :?> 'a
    if (r <> value) then
        printfn "Changing value"
        source.GetType().GetProperty(property).SetValue(source, value, null)

You can call someObj?TheProperty <- newVal, and you'll see the "Changing value" print only if the value has actually changed.

Internally, this works by using reflection to get the old value of the property and set the new one, so it's not going to have the best performance characteristics, but as you're using it to raise property changed type notifications, that's likely not a problem.

like image 41
Reed Copsey Avatar answered Oct 16 '22 22:10

Reed Copsey