Search code examples
smalltalkpharo

What is the idiomatic way to define an enum type in Smalltalk?


Like in Java, C# etc. How would I define something like enum Direction { input, output } in Smalltalk?


Solution

  • Trivial Approach

    The most trivial approach is to have class-side methods returning symbols or other basic objects (such as integers).

    So you can write your example as follows:

    Direction class>>input
        ^ #input
    
    Direction class>>output
        ^ #output
    

    And the usage:

    Direction input
    

    The main downsides are:

    • any other "enum" that happens to return the same value will equals to this one, even though the enums are different (you could return e.g. ^ self name + '::input')
    • during debugging, you see the actual value of the object, which is especially ugly for number-based enums (Uh... what does this 7 mean?)

    Object Approach

    A better way is to create your own enum object and return instances of it.

    Such object should:

    • override = and hash, so you can compare them by your value and use the enum as key in hashed collections (dictionaries)
    • store the actual unique value representation
    • have custom printOn: method to ease debugging

    It could look something like this

    Object subclass: #DirectionEnum
        slots: { #value }
        classVariables: {  }
        category: 'DirectionEnum'
    
    "enum accessors"
    DirectionEnum class>>input
        ^ self new value: #input
    
    DirectionEnum class>>output
        ^ self new value: #output
    
    "getter/setters"
    DirectionEnum>>value
        ^ value
    
    DirectionEnum>>value: aValue
        value := aValue
    
    "comparing"
    DirectionEnum>>= anEnum
        ^ self class = anEnum class and: [ self value = anEnum value ]
    
    DirectionEnum>>hash
        ^ self class hash bitXor: self value hash
    
    "printing"
    DirectionEnum>>printOn: aStream
        super printOn: aStream.
        aStream << '(' << self value asString << ')'
    

    the usage is still the same

    Direction input.
    DirectionEnum output asString. "'a DirectionEnum(output)'"
    

    and the problems mentioned in the trivial approach are resolved.

    Obviously this is much more work, but the result is better. If you have many enums, then it could make sense to move the basic behavior to a new superclass Enum, and then DirectionEnum would just need to contain the class-side methods.