Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In Smalltalk, what’s the best way of defining a commutative binary method when the sender and argument are of different types?

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)?

like image 517
user10264746 Avatar asked May 01 '21 07:05

user10264746


2 Answers

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.

like image 161
Uko Avatar answered Oct 10 '22 09:10

Uko


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

like image 36
aka.nice Avatar answered Oct 10 '22 11:10

aka.nice