Zipping over Magritte-described Objects
Magritte is a metamodel description framework for Smalltalk, that is, a way of describing your domain objects. Having a description of your domain objects allows you to do a bunch of neat things, like automatically building a Seaside form for displaying an object.
If you’ve used C#’s ASP.Net MVC framework, you might think of the Magritte description as the collection of annotations your Model or ViewModel objects might have. Instead of
public class Person {
[DisplayName("First Name")]
public string FirstName { get; set; }
[DisplayName("Last Name")]
public string LastName { get; set; }
}
you have
Person class>>descriptionFirstName
^ MAStringDescription new
label: 'First Name';
yourself
Person class>>descriptionLastName
^ MAStringDescription new
label: 'Last Name';
yourself
Magritte allows you to describe more than how to render a field, or in what order to render fields. It allows you to describe how to access the parts of an object, whether directly calling its selector, plugging into a chain of accessors, or a memento-decorated object, and so on. Magritte can also describe the relationships between objects, like one-to-one or one-to-many.
Magritte provides us with a uniform way of accessing parts of an object, without resorting to reflection.
How would one zip over a tree of Magritte-described objects? Building on our previous work it’s not hard at all: we have an n-ary tree of objects. First, we make sure we fully annotate our domain objects with Magritte descriptions.
Next, we do two things: first, Magritte allows us to serialise all our children as an
OrderedCollection
:
MZBase>>children
^ self description children collect: [:each | each accessor read: self].
and so we can unserialise this collection as a newly-instantiated object:
Object>>withValues: aCollection
^ self new.
MZBase>>withValues: aCollection
"Conceptually this mirrors Scala's apply, in a variadic manner."
| o |
"Create a new object, of the appropriate type."
o := self new.
"Initialise it. Each setter returns a _new instance_ so while functionally
pure, this isn't exactly efficient."
"#with:do: is a pair-wise version of #do:."
o description children with: aCollection do:
[:acc :val | o := acc accessor write: val to: o].
^ o.
We need to adjust our original
TreeZipper
, which had a few hard-coded bits. We change references to
TreeZipper new
to
self class new
, and instead of creating
ZTree
s with
ZTree value: foo children: bar
we allow subclasses to create their own objects:
self newFocusOn: foo children: bar
. In particular, we say
TreeZipper subclass: #MagritteZipper.
MagritteZipper>>newFocusOn: anObject children: aCollection
^ aCollection isEmpty
ifTrue: [anObject]
ifFalse: [anObject class withValues: aCollection].
We’re being a bit tricky here: if aCollection is empty, we have a “primitive” value – something with no subcomponents, like a number or a
String
[1] – while a non-empty
aCollection
indicates some composite object: an
MZBase
object.
And that’s it!
| p p2 z |
p := MZPerson firstName: 'Foo' lastName: 'Bar' at: (MZAddress at: 'foo').
z := MagritteZipper on: p.
p2 := ((z down changeTo: 'Barzzz') right changeTo: 'Brzz') root.
p2 firstName --> 'Barzzz'
As always, everything’s available at SqueakSource. Here’s the load script:
Installer ss
project: 'Seaside31';
install: 'Grease-Core-pmm.39';
install: 'Grease-Pharo-Core-pmm.22'.
Installer lukas
project: 'magritte2';
install: 'Magritte-Model-fbs.405';
install: 'Magritte-Pharo-Model-lr.22'.
Installer ss
project: 'Zippers';
install: 'Zippers-fbs.35'.
Installer ss
project: 'MagZip';
install: 'MagZip-fbs.2'.
[1] Yes, a
String
’s actually an
OrderedCollection
of
Character
, but usually the individual characters aren’t interesting, so we treat it as a primitive value.
