How do you, in an instance method, parametrize a reference to the property of an instance of some other class (as opposed to its value) ? The question came as I wrote the code below (putting Observer pattern into place but am having declaration issues; b.observe needs fixing, obviously) and I can’t quite put my finger on the idiomatic way to do this.
class A
include Observable
getter some_prop : Bool = false
end
class B
include Observer
def observe(prop : Pointer(T))
# todo
end
end
a = A.new
b = B.new
b.observe(pointerof(a.some_prop))
According to Google’s AI overview, this cannot be done in Crystal, other than by wrapping the value in an instance of some other class, something I prefer to avoid. Will try from another angle.
Not sure I understand what exactly you want to do.
Taking a pointer of a method call (like the getter) is not possible. But you can take a pointer of the instance variable itself: pointerof(a.@some_prop).
Maybe that’s what you’re looking for?
This is more or less how I’ve seen observables and observers implemented in the past:
module Observable
private getter observers = [] of Observer
macro observable_property(var)
property {{var}}
def {{var.var}}=(value : {{var.type}})
previous_def
notify
end
end
def notify
observers.each(&.changed(self))
end
def add_observer(observer : Observer)
observers << observer
end
end
module Observer
def observe(observable : Observable)
observable.add_observer self
end
end
class A
include Observable
observable_property some_prop : Bool = false
end
class B
include Observer
def changed(a : A)
pp changed: a
end
end
a = A.new
b = B.new
b.observe a
a.some_prop = true
The Observable needs to know which objects to notify on change, so it keeps a list of them. Then when it performs some observable operation (in this case, an update to a property, it calls notify to tell all the Observers that it changed.
Something to keep in mind: expected behavior will work as long as the property updates are done through the dedicated setter method. Observers will not be notified of updates done via an @ivar assignment.
Indeed. If you bypass that method, for example if you’re performing a batch updates and only want to ping the observers once for the whole batch, you’re on the hook to call notify yourself.
I’ve been wondering for a long time whether it would be useful (in Crystal) to have some kind of “hook” to find out when an ivar has been changed. Usefull for ORMs (automatic detection when some ivar/property was changed), implementing something like “reactive properties” etc. etc.