adius
adius

Reputation: 14962

How to add same method with 2 different names in GNU Smalltalk?

How can I have a class expose the same method with 2 different names?

E.g. that the asDescripton function does the same thing / re-exports the asString function without simply copy-pasting the code.

Object subclass: Element [
  | width height |

  Element class >> new [
    ^super new init.
  ]

  init [
    width := 0.
    height := 0.
  ]

  asString [
    ^ 'Element with width ', width, ' and height ', height.
  ]

  asDescription [ "???" ]
]

Upvotes: 2

Views: 154

Answers (2)

Leandro Caniglia
Leandro Caniglia

Reputation: 14858

In Smalltalk you usually implement #printOn: and get #asString from the inherited version of it which goes on the lines of

Object >> asString
  | stream |
  stream := '' writeStream.
  self printOn: stream.
  ^stream contents

The actual implementation of this method may be slightly different in your environment, the idea remains the same.

As this is given, it is usually a good idea to implement #printOn: rather than #asString. In your case you would have it implemented as

Element >> printOn: aStream
  aStream
    nextPutAll: 'Element with width ';
    nextPutAll: width asString;
    nextPutAll: ' and height ';
    nextPutAll: height asString

and then, as JayK and luker indicated,

Element >> asDescription
  ^self asString

In other words, you (usually) don't want to implement #asString but #printOn:. This approach is better because it takes advantage of the inheritance and ensures consistency between #printOn: and #asString, which is usually expected. In addition, it will give you the opportunity to start becoming familiar with Streams, which play a central role in Smalltalk.

Note by the way that in my implementation I've used width asString and heigh asString. Your code attempts to concatenate (twice) a String with a Number:

'Element with width ', width, ' and height ', height.

which won't work as you can only concatenate instances of String with #,.

In most of the dialects, however, you can avoid sending #asString by using #print: instead of #nextPutAll:, something like:

Element >> printOn: aStream
  aStream
    nextPutAll: 'Element with width ';
    print: width;
    nextPutAll: ' and height ';
    print: height

which is a little bit less verbose and therefore preferred.

One last thing. I would recommend changing the first line above with this one:

    nextPutAll: self class name;
    nextPutAll: ' with width ';

instead of hardcoding the class name. This would prove to be useful if in the future you subclass Element because you will have no need to tweak #printOn: and any of its derivatives (e.g., #asDescription).

Final thought: I would rename the selector #asDescription to be #description. The preposition as is intended to convert an object to another of a different class (this is why #asString is ok). But this doesn't seem to be the case here.

Addendum: Why?

There is a reason why #asString is implemented in terms of #printOn:, and not the other way around: generality. While the effort (code complexity) is the same, #printOn: is clearly a winner because it will work with any character Stream. In particular, it will work with no modification whatsoever with

  1. Files (instances of FileStream)
  2. Sockets (instances of SocketStream)
  3. The Transcript

In other words, by implementing #printOn: one gets #asString for free (inheritance) and --at the same time-- the ability to dump a representation of the object on files and sockets. The Transcript is particularly interesting because it supports the Stream protocol for writing, and thus can be used for testing purposes before sending any bytes to external devices.

Remember!

In Smalltalk, the goal is to have objects whose behavior is simple and general at once, not just simple!

Upvotes: 5

JayK
JayK

Reputation: 3141

As lurker wrote in the comments, send the asString message in asDescription.

asDescription
    ^ self asString

This is usually done to expose additional interfaces/protocols from a class, for compatibility or as a built-in adapter. If you create something new that does not have to fit in anywhere else, consider to stick to just one name for each operation.

Edit: if you really are after the re-export semantics and do not want the additional message sends involved in the delegation above, there might be a way to put the CompiledMethod of asString in the method dictionary of the class a second time under the other name. But neither am I sure that this would work, nor do I know the protocol in GNU Smalltalk how to manipulate the method dictionary. Have a look at the documentation of the Behavior class. Also, I would not consider this as programming Smalltalk, but tinkering with the system.

Upvotes: 2

Related Questions