Traits provide a means of composing an object’s functionality out of smaller, simpler parts. Verbs being limited in number, especially good ones, sometimes you want to compose two traits that share names. Today we’re going to look at how to resolve the dispute.

I have a soft spot for abstract algebra. I realised how we can consider a field to be the melding of two separate groups: we can construct the field of reals from the group of integers under + together with the group of integers under *, with a bit of stitching in the form of the distributivity laws. So let’s make a group:

Trait named: #TGroup

uses: #()

category: 'Group-Theory'.

TGroup >> * anObject

"Return the result of applying the group operation to self and anObject."

self requirement.

TGroup >> inverse

self requirement.

TGroup >> identity

^ self class identity.

TGroup classTrait >> identity

self requirement.

uses: #()

category: 'Group-Theory'.

TGroup >> * anObject

"Return the result of applying the group operation to self and anObject."

self requirement.

TGroup >> inverse

self requirement.

TGroup >> identity

^ self class identity.

TGroup classTrait >> identity

self requirement.

Now we can compose the two traits. Er, except we’re going to compose `TGroup`

with itself! Name clashes galore! The `uses:`

argument to Trait creation shows a `TraitComposition`

, wherein we show how the two group structures contribute to the field structure. ^{[1]}. We thus have

Trait named: #TField

uses: TGroup @ {#identity->#zero. #* -> #+. #inverse->#negated} +

TGroup @ {#identity->#one. #inverse->#reciprocal}

category: 'Group-Theory'

uses: TGroup @ {#identity->#zero. #* -> #+. #inverse->#negated} +

TGroup @ {#identity->#one. #inverse->#reciprocal}

category: 'Group-Theory'

Note that aliasing – `#identity -> #zero`

– doesn’t remove the method from the Trait. This causes a bit of a problem. In the context of a field, we don’t usually talk of “the identity element”, because of course there are two group operations. We instead talk of the additive identity (“zero”) and the multiplicative identity (“one”). We call one operation “sum” or “+”, and the other we call “product” or “*”. Similarly, the two inverse operations are “unary negation” or “negated”, and “reciprocal”.

The only thing I dislike about composing the two `TGroup`

s in this way is that aliasing doesn’t remove the conflicting methods: “identity” and “inverse” remain in the public API of `TField`

. But we can fix this, by excluding the ambiguous selectors:

Trait named: #TField

uses: TGroup @ {#identity->#zero. #* -> #+. #inverse->#negated} - {#identity. #inverse} +

TGroup @ {#identity->#one. #inverse->#reciprocal} - {#identity. #inverse}

category: 'Group-Theory'.

TField classTrait

uses: TGroup classTrait - {#identity} + TGroup classTrait - {#identity}.

uses: TGroup @ {#identity->#zero. #* -> #+. #inverse->#negated} - {#identity. #inverse} +

TGroup @ {#identity->#one. #inverse->#reciprocal} - {#identity. #inverse}

category: 'Group-Theory'.

TField classTrait

uses: TGroup classTrait - {#identity} + TGroup classTrait - {#identity}.

We can’t avoid the awkward double-phrasing of the exclusion (`-`

). It would be much nicer if you could remove the methods from the *composition*:

(TGroup @ {#identity->#zero. #* -> #+. #inverse->#negated} +

TGroup @ {#identity->#one. #inverse->#reciprocal}) - {#identity. #inverse}

TGroup @ {#identity->#one. #inverse->#reciprocal}) - {#identity. #inverse}

but neither Squeak’s nor Pharo’s implementation of Traits permit this: exclusion removes methods only from the rightmost trait. Whether that’s simply a bug (in the sense of “gosh, no one’s thought of doing that”) or something intrinsic in the semantics, is as yet an open question.

[1] Of course properties like the distributive laws of + and * aren’t defined here. For that you could use something like SqueakCheck.

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