8

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

Michael
  • 4,722
  • 6
  • 37
  • 58
  • I'm not a Java programmer so I'm not sure whether you are referring to a way to define constants in Smalltalk. If that's the case, you should create a `PoolDictionary` and declare the constants there. I can elaborate on this if you confirm that my interpretation is correct. – Leandro Caniglia Mar 23 '18 at 14:55
  • Mo an enum is a type such that a variable of type enum X can only have a set of values defined for X - e.g. in this case `x: = new Direction` means that x is input or ouput abd no possibilty of being anything else. – mmmmmm Mar 23 '18 at 15:23

4 Answers4

7

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.

Peter Uhnak
  • 9,617
  • 5
  • 38
  • 51
  • 1
    This is a very nice answer. I would just add that there are probably three reasons of why you need enums. 1) you want to have a programmatic name for some constant (that can be use by such tools as autocomplete or refactoring browser) and trivial approach is good for that; 2) you want to have a different functionality for a certain kind of an “enum” and here is where the object approach shines; 3) you want to restrict what is assigned to a variable: I general you don’t want to have this in smalltalk, but you can always add assertions to setters or use a custom validating Slot in Pharo – Uko Mar 26 '18 at 07:13
6

The closest Smalltalk feature to an enum type is the SharedPool (a.k.a. PoolDictionary). Therefore, if you are porting some enum from, say, Java to Smalltalk, you might want to use a SharedPool. Here is how to do so:

For every enum in your type you will define an association in the pool with key the type name and value the type value.

In some dialects PoolDictionaries are dictionaries, in Pharo they are subclasses of SharedPool. In Pharo, therefore, you declare all the type names as class variables. Then you associate values to keys in an initialization method (class side).

For example, you could define a subclass of SharedPool named ColorConstants with the class variables 'Red', 'Green', 'Blue', 'Black', 'White', etc., like this:

SharedPool
  subclass: #ColorConstants
  instanceVariableNames: ''
  classVariableNames: 'Red Green Blue Black White'
  poolDictionaries: ''
  package: 'MyPackage'

To associate names with values, add a class side initialization method on the lines of:

ColorConstants class >> initialize
  Red := Color r: 1 g: 0 b: 0.
  Green := Color r: 0 g: 1 b: 0.
  Blue := Color r: 0 g: 0 b: 1.
  Black := Color r: 0 g: 0 b: 0.
  White := Color r: 1 g: 1 b: 1.
  "and so on..."

Once you evaluate ColorConstants initialize you will be able to use the pool in your class

Object
  subclass: #MyClass
  instanceVariableNames: 'blah'
  classVariableNames: ''
  poolDictionaries: 'ColorConstants'
  package: 'MyPackage'

In MyClass (and its subclasses) you can refer to the colors by name:

MyClass >> displayError: aString
   self display: aString foreground: Red background: White

MyClass >> displayOk: aString
   self display: aString foreground: Green background: Black
Leandro Caniglia
  • 14,495
  • 4
  • 29
  • 51
3

For simple cases, just use symbols as Peter suggested - you could also just store the possible values in an IdentityDictionary.

If you mean the more powerful kind of enums that are available in Java (where they can be more than just a type of named constant; they can have behaviour, complex attributes, etc.), then I'd take it a step further than Peter and just create a subclass for each enum type. Even if you're talking a large number of enums (your use case seems to need only two), don't be afraid of having many subclasses that only have one or two methods in them that are just used to differentiate them from each other, with most of the work done in the common superclass.

Amos M. Carpenter
  • 4,848
  • 4
  • 42
  • 72
2

Since Smalltalk is dynamically typed, you cannot restrict the value of a variable to a subset of objects anyway, so there is no difference between enum members and global constants, except for the namespacing through the enum name.

Edit: For options how to define your enum constants, see Peter's answer. Just let me mention that you can also use symbols directly if it is sufficient for your needs. direction := #input. direction := #output

JayK
  • 3,006
  • 1
  • 20
  • 26