Enumerations in EK9

The 'Enumeration' was briefly outlined in the structure section. It is shown in more detail here in the form of an example. This shows the operators that are built in to every enumeration as standard.

The idea of an enumeration in general is very simple, it is a predefined list of values. EK9 considers the list to be an ordered list, with the value being the order they are define. Note EK9 does not use the keyword word enumeration but just considers them to be a simple type.

Enumeration Example

The example below shows how enumerations can be used and combined with records and functions. It shows all the operators available and how each of the values of and enumeration can be 'streamed'. Importantly it shows how you can obtain an enumerated value from a String (but note if the String does not represent any value the result is unset).

The EK9 is Set nature also applies to enumerations, this is quite dissimilar to other programing languages. So in EK9 it is possible to have an enumerated value this is actually none of the valid enumerated values, i.e. it is unset.

#!ek9
module introduction
  defines type

    CardRank
      Two
      Three
      Four
      Five
      Six
      Seven
      Eight
      Nine
      Ten
      Jack
      Queen
      King
      Ace

    CardSuit
      Hearts
      Diamonds
      Clubs
      Spades

  defines record

    Card
      rank as CardRank: CardRank()
      suit as CardSuit: CardSuit()

      operator ?
        <- rtn as Boolean: rank? and suit?
      operator $
        <- rtn as String: $rank + " of " + $suit
      operator #^
        <- rtn as String: $this

  defines function

    cardCreator() as abstract
      -> rank as CardRank
      <- rtn as Card

    fullRankCreator()
      -> suit as CardSuit
      <- rtn as List of Card: cat CardRank
        | map with (suit) is cardCreator (rtn: Card(rank, suit))
        | collect as List of Card

  defines program

    ShowOperators()
      stdout <- Stdout()

      hearts <- CardSuit.Hearts
      clubs <- CardSuit.Clubs

      assert hearts != clubs

      assert hearts == #< clubs

      assert CardSuit.Spades == #> clubs

      unknownSuit <- CardSuit()

      assert ~unknownSuit?

      asString <- $hearts

      assert asString == "Hearts"

      assert hearts < clubs

      assert clubs <=> hearts > 0

      invalidSuit <- CardSuit("NonSuch")
      assert ~invalidSuit?

      validSuit <- CardSuit("Clubs")
      assert validSuit?

      validRank <- CardRank("Ace")
      assert validRank?

      for rank in CardRank
        message <- switch rank
          <- rtn as String: String()
          case CardRank.Ace
            rtn: "Wow very lucky " + $rank
          case > CardRank.Ten
            rtn: "Not too bad " + $rank
          case > CardRank.Five
            rtn: "Oh dear me " + $rank
          default
            rtn: "Not so lucky " + $rank
        stdout.println(message)

    DeckOfPlayingCards()
      stdout <- Stdout()

      cat CardSuit > stdout
      cat CardRank > stdout

      suits <- cat CardSuit | collect as List of CardSuit

      for suit in CardSuit
        for rank in CardRank
          stdout.println(Card(rank, suit))

      cat CardSuit | map with fullRankCreator | flatten > stdout
//EOF

The Enumerations

There are two enumerations defined. This example is based around playing cards. So there is the CardSuit and the CardRank. The values of which are obvious (Aces high). As you can see, they are just a list of values! But in a list under the name of the enumeration.

Using the Enumerations

To represent a playing card, both of the enumerations have been used as fields/properties on a record. To make the record a little more useful; the operators is set, to string and promote have been implemented. A class could have been used for this, but in this example a record will do.

Functions

The abstract function cardCreator is used in fullRankCreator as part of the dynamic function deckPopulator. If the use of functions and dynamic functions is not something you've read about yet then jump over to those sections and read up on them. An explanation on how these are used is given in the the following paragraphs relating to the program DeckOfPlayingCards.

Operators

All the supported operators on enumerations are shown in program ShowOperators. These are all the sort of operators you would probably expect:

But also note the mechanism to obtain an enumerated value from a String.

Enumerated values in a Switch

Many programming languages, just as Java and C# tend to promote the use of methods on enumerations. This is not possible (nor desirable - opinion) in EK9. As previously stated EK9 does not permit methods on enumerations. Instead it promotes the use of composition, so in the example above you can see the use of a switch statement that writes out some appraisal of the card drawn.

The EK9 approach is to separate the enumerated value and some sort of business logic (in this case the appraisal of the card drawn). If there needs to be some close binding, then use a class with a property of the enumeration and a method that has the appraisal logic. Alternative maybe one or more functions could be used to make the appraisal. It is also possible to use a combination of a class and a function delegate in combination.

If you wished to augment the enumeration with other supporting information (may be an image in the case of playing cards), then use a Dictionary with the key being the enumerated value and the value being the image. Again this approach uses composition, which in general is more flexible in the longer term. You might argue this approach is less Object Oriented (which it is), but in EK9 you have the choice to use classes or just data structures and functions to operate on them.

Have a read of some quotes by Alan Perlis. Specifically "It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures". The point here is that there alternative mechanisms than just Objection Orientation to structure solutions when designing software.

Program DeckOfPlayingCards

This program is designed to highlight how it is possible access all the enumerated values. The first two examples just show how each of the enumerated values of the CardSuit and CardRank can be streamed through a pipeline process, converted to a String and printed to 'stdout'. The third demonstrates how an enumeration can be converted to a 'List of ...', should it be necessary.

The final part of the program is a little more involved and interesting. It starts off by showing a double nested loop that creates a Card and prints the details of that Card to stdout. As you can see this is fairly straight forward, using the enumeration type as if it were a collection of values (which in a way it is).

The last line of the example shows the same functionality as the previous double nested loop but in a functional manner. The CardSuit being streamed into a map function called fullRankCreator.

The fullRankCreator is designed to accept each CardSuit as it is sent into the pipeline. It then creates a dynamic function (a bit like a lambda in some ways). Importantly this dynamic function captures the suit. The other key point here is that the dynamic function actually extends the abstract function cardCreator. Now the fullRankCreator uses the newly created dynamic function by streaming each of the CardRanks into it.

The dynamic function uses the captured CardSuit and the streamed CardRank to create a Card record. As the newly created Cards come out of the pipeline they are piped into rtn with is a List of Card.

Finally in the program, a List of Card comes out of the pipeline for each CardSuit, but as we really just want all the Cards to be printed to 'stdout', we use flatten to take each card out of the List of Card so they can be sent to 'stdout'. Now as we wrote a promote operator on the Card record that promotes the Card object to a String so it can be just printed.

Summary

Whilst this section is really about enumerations (which are very simple) it seemed more useful to show how enumerations could/should actually be used. Here the contrast between a more functional approach has been taken with streaming pipeline processing, functions and dynamic functions with closure/capture of variables. This can be seen in comparison to the double nested loop of CardSuit and CardRank.

If the processing like the above example is simple; then a more procedural approach is probably a better option. With EK9 you have the choice of either styles. But if you find yourself will lots of complex logic deep in nested loops or need to break out of them, then consider a more functional approach. But with EK9 you have a choice of functional and object oriented or a blend of the two.

Next Steps

The next section on Records shows the first aggregate type.