Watch your instance variables!

Julien Delplanque
Concerning Pharo
Published in
4 min readMar 19, 2020

As a follow-up of my previous article related to Pharo slots, I will show you another killer feature that comes with Slots: the possibility to be notified when the value of a slot changes. This feature comes from anew kind of slot introduced in Pharo 8. This slot is named ObservableSlot and here is a short blogpost about it.

Reflexive duck watcher, for the sake of illustration. (source)

What are Slots?

You can find some explanations about what are Slots in my previous article. For lazy readers, here is a quick reminder:

A slot is a meta-object for accessing data held by an Object.

It defines a protocol to read (#read:) and to write (#write:to:) values.

Slots allow developers to control what happens when an instance variable is read or written. How? Simply by providing hooks allowing developers to control the byte-code generated when instance variable is read or written.

Observable slot

For the needs of Spec (a GUI framework in Pharo), ObservableSlot have been implemented. This kind of slot allows one to watch changes happening on an instance variable. As you can guess, in the context of a GUI framework it is super-useful. One can watch changes made to an instance variable and update the user interface accordingly.

From a user perspective, two components are important to use this feature:

  • The ObservableSlot class: Implements a slot with notification mechanism.
  • The TObservable trait: Adds convenient methods to let a listener subscribe to changes happening on the value held by the ObservableSlot.

Let’s dig into an example to illustrate the usage of ObservableSlot and TObservable.

Example

Consider you model a Person. Let’s say a person has a name and an age.

Object subclass: #Person
slots: { #name. #age }
classVariables: { }
package: 'ObservableSlotBlogPost'

If you need to watch for changes made to the age of a person, you can change the slot #age to become an ObservableSlot like this:

Object subclass: #Person
slots: { #name. #age => ObservableSlot }
classVariables: { }
package: 'ObservableSlotBlogPost'

The #=> message send to a symbol creates a slot of a specific kind by passing the slot class as argument. If you just write a symbol, an InstanceVariableSlot is created by default.

Now that we have the ObservableSlot, we need a way to register to changes happening to #age slot. This is exactly the service offered by TObservable trait. Let us just apply it on Person class.

Object subclass: #Person
uses: TObservable
slots: { #name. #age => ObservableSlot }
classVariables: { }
package: 'ObservableSlotBlogPost'

This trait adds the following methods:

  • observablePropertyNamed: : Returns the ObservableSlot having the name provided as argument.
  • property:rawValue: : Writes a value (second argument) into the ObservableSlot having the name provided as first argument without notifying watchers.
  • property:whenChangedDo: : Register a block to be called (second argument) when the value of the ObservableSlot having the name provided as first argument is modified.

Ok, now we can listen to changes happening to the #age slot of Person instances. Let us see what we can do with that feature.

First of all, let’s create a person named Julien and being 25 years old:

me := Person new
name: 'Julien';
age: 25;
yourself.

Because of the observable slot, it is possible to listen to changes happening on the age of Julien:

me property: #age whenChangedDo: [ :newAge | 
Transcript show: 'Julien''s new age is ' , newAge asString; cr ].

Here, we just log something in the transcript when the age is updated.

So, if we modify Julien’s age as follow:

me age: 26.
me age. "26"

We get the following message in the transcript:

Pretty cool no?

It is also possible to modify Julien’s age without executing the block that logs the message in the transcript.

me property: #age rawValue: 25.
me age "25"

Limitations

Every good thing has limitations. For ObservableSlot, I see two things.

First, there can be a single “listener” to changes happening on the instance variable. However, handling multiple listeners can be achieved in the block being called when the value change but this is the responsibility of the developer using the ObservableSlot.

Second, for now, there is no standard way to watch for changes happening inside a collection stored in an instance variable. For example, one might want to be notified when a collection get an object removed. This feature is not supported but it will probably come in the future.

Conclusion

ObservableSlot provides a simple and nice mechanism to watch changes happening on an instance variable. It is simple to set-up and prevent developers to write the boilerplate code required when it comes to implement a similar behaviour “by hand”.

--

--