Seeking a reference to an object property

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))
1 Like

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.

1 Like

Macros to the rescue… The language never ceases to surprise.

Thanks for this. Will adapt.

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.

That’s what setters are for.

2 Likes