smalltalkgnu-smalltalk

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 [ "???" ]
]

Solution

  • 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!