Suppose you have a class Foo, and that you want to be able to multiply a Foo by a Number to get another Foo, using ‘@@‘ as the multiplication sign.
Since multiplication is commutative, it would be nice to be able to write:
| f a b |
f := Foo new.
a := 3 @@ f.
b := f @@ 3.
self assert: a = b
This requires not just adding the binary method “@@” to Foo, but also to the Number class. So you end up with essentially the same method in two different places (along with a circular dependency), which seems rather inelegant.
So I'm wondering, in Smalltalk, is there any other way to create commutative binary methods where the sender and argument are of different types - one which doesn’t require you to define the same message in two different classes?
If not, is possible to create this ability using Smalltalk itself (ie. add classes/methods which automate the management of commutative binary methods, without changing the actual Smalltalk language or VM)?
In your case it's also worth questioning what happens if you send not a number the parameter of the @@
message to an instance of Foo
.
E.g:
f @@ 'hello'
To omit that, you can use double dispatch. So you define a method that multiplies a number:
Foo>>#multiplyWithANumber: aNumber
"do multiplication with a number"
Then in the object hierarchy, you define the entry points of @@
Object>>#@@ aFoo
"signal some error saying that this operation is not supported"
self shouldNotImplement
Number>>#@@ aFoo
^ aFoo multiplyWithANumber: self
Foo>>#@@ anObject
"pass decision to the parameter"
"also, what should happen if anObject is a Foo"
^ anObject @@ self
This may be overcomplicated, you in a simple case if you care less about the types, and want to avoid duplication, you can have:
Foo>>#multiplyWithANumber: aNumber
"do multiplication with a number"
Foo>>#@@ aNumber
^ self multiplyWithANumber: aNumber
Number>>#@@ aFoo
^ aFoo multiplyWithANumber: self
Of course you can skip multiplyWithANumber:
all-together and just have one @@
with implementation (probably on the Foo side, because it's the main reason of this implementation), and another @@
which just calls the @@
with implementation. I like to have a verbose method so it's clear what is going on and you don't have to write additional comments.
In Smalltalk, the rules are simple: the message is interpreted by the reeiver, and a method is looked-up in receiver class, then superclasses.
In case of binary message, if we want to dispatch to a specific method depending on both receiver and argument types, then a well known pattern is to use double-dispatching as demonstrated in Uko's answer.
Foo>>op: b
^b opFromFoo: self
Bar>>op: b
^b opFromBar: self
The problem is now that you may have two implementations of the same math. operation:
Foo>>opFromBar: b
"operate on a Foo and a Bar"
...snip...
Bar>>opFromFoo: b
"operate on a Bar and a Foo"
...snip...
Possible workarounds 1: since you know that op: is commutative, let one dispatch to the other:
Foo>>opFromBar: b
"op: is commutative, let Bar do the job"
^b opFromFoo: self
Bar>>opFromFoo: b
"operate on a Bar and a Foo - do the real work"
...snip...
You don't have to duplicate the core, but still have to define n*n dispatch methods for n different types...
Workaround 2: reify the operation in its own class
OpAlgo>>opFoo: a andBar: b
"perform op with a Foo and a Bar"
That still require double dispatching (n*n methods), and may leak internal implementations detail of Foo Bar, plus OpAlgo is kind of a utility with no real state.
Workaround 3: implement a multiple-dispatch in Smalltalk. It would be too long to devise a solution here, but you'll find references on the net, like http://www.laputan.org/reflection/Foote-Johnson-Noble-ECOOP-2005.html for example
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With