F # - Set property if changed

I have a class with properties that have several side effects that fire when its setters are called, such as change events and / or rendering actions. How can I create a function that will set a property only if its value needs to be changed?

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, volatile properties are not refs, and I cannot find a way to create such a thing that will compile. For instance,

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

just tell me that oldVal doesn't change.

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

+6
source share
2 answers

I do not think that there is an easy way to do this "for free."

One trick that can make this a little easier is that you can really treat getter and setter properties as functions, so you can write setIfChanged as a function that performs two functions:

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

Given class A with proeperty P , you can call this with 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 can be a bit confusing for many people. For completeness, here we use the definition of A , which I used for testing:

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

A more sophisticated approach would be to use quotes and reflections, which will be slower, but this will allow you to write something like setIfChanged <@ aP @> 42 .

+5
source

You can do:

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

Based on your explanation of goals, I would recommend looking at Gjallarhorn . It provides signaling support over variable values ​​and is designed to be extremely flexible in terms of what happens when the value changes. It can be used as the main mechanism by which you can easily create your own signals when something changes (if it does not yet provide what you need with the View module).


Edit:

Given your comment that the property in question is on the third side, one option would be to use dynamic support in F # to override the op_DynamicAssignment ( ?<- ) operator to dynamically send to a method, but raising your β€œnotification” in this 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 will see that the "Change value" value will only be printed if the value has really changed.

Internally, this works by using reflection to get the old value of the property and set the new one, so it will not have better performance characteristics, but since you use it to receive notifications about the change of the property type, this is probably not a problem.

+4
source

Source: https://habr.com/ru/post/987963/


All Articles