technology from back to front

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 ZTrees 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.

by
Frank Shearar
on
09/06/11
 
 


9 − = three

2000-14 LShift Ltd, 1st Floor, Hoxton Point, 6 Rufus Street, London, N1 6PE, UK+44 (0)20 7729 7060   Contact us