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?
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
.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With