Enumerations are datatypes that enumerate some values.

Python

Within Python, which is object-oriented, enumerations are specialised classes representing choices between a set of values.

For example, consider the following enumerated object to represent different sizes.

class Size(Enum):
    SMALL  = 1
    MEDIUM = 2
    LARGE  = 3

We can use the match statement in Methods to deconstruct enums, as well as other types:

def toString(s : Size) -> str:
    match s:
        case Size.SMALL:
            return "Small"
        case Size.MEDIUM:
            return "Medium"
        case Size.LARGE:
            return "Large"
        case _:
            return "Default"

The keyword match indicates we are matching on what s could be.

We delineate each possible ‘case’ with the keyword case, followed by the name of the constructor we want to examine.

If we do not want to examine any more cases we can use the wildcard pattern: _.

For example:

def toString(s : Size) -> str:
    match s:
        case Size.SMALL:
            return "Small"
        case _:
            return "Default"

Dafny

We begin with the example of an enumerated type to represent different sizes.

datatype Size
  = Small
  | Medium
  | Large

Here the keyword datatype indicates that we are creating a new datatype which we have named Size.

The equal sign indicates that we are then to define the datatypes’ data constructors, each separated by the pipe symbol |.

For Size, these constructors are: Small, Medium, and Large.

Let us move onto Pattern Matching.

To deconstruct datatypes we can use the match statement in Methods, and expression in functions.

method ToString(s : Size) returns (str : string)
{
  match s
  {
  case Small => return "Small";
  case Medium => return "Medium";
  case Large => return "Large";
  }
}

The keyword match indicates we are matching on what s could be.

We delineate each possible ‘case’ with the keyword case, followed by the name of the constructor we want to examine.

If we do not want to examine any more cases we can use the wildcard pattern: _.

For example:

method ToString(s : Size) returns (str : string)
{
  match s
  {
  case Small => return "Small";
  case _ => return "Do not care";
  }
}

We can also use match in the functional fragment of Dafny

function toString(s : Size) : string
{
  match s
  {
  case Small => "Small"
  case Medium => "Medium"
  case Large => "Large"
  }
}

There is little difference between Dafny’s two languages. It is easy to make mistakes.

Remember that match in Dafny’s functional fragment is an expression. Which means we can use it where expressions are expected. For example, consider the following rewrite of ToString2.

method ToString3(s : Size) returns (str : string)
{
  return match s
         {
         case Small => "Small"
         case _ => "Do not care"
         }
         ;
}

Idris

Idris, like all functional languages, supports algebraic datatypes aka ‘sums of products’. An algebraic datatype is where we are creating choices between different ways we can say what the datatype is. We show this more in later sections on Unions

Datatypes in Idris, however, are not quite algebraic datatypes. In Idris, datatypes are indexed families, a type constructor (how we construct types) paired with several data constructors.

We begin with the example of an enumerated type to represent different sizes.

data Size
  = Small
  | Medium
  | Large

Here the keyword data indicates that we are creating a new datatype which we have named Size.

The equal sign indicates that we are then to define the datatypes’ data constructors, each separated by the pipe symbol |.

For Size, these constructors are: Small, Medium, and Large.

The same as in Dafny!

We can rewrite MaybeStr as:

data MaybeStr : Type where
  Nothing : MaybeStr
  Just : (value : String) -> MaybeStr

The above, long, definition is what the original definition of MaybeStr is transformed too internally!

Let us now consider pattern matching.

To deconstruct datatypes we can use case expressions, or pattern matching in function equations.

toString : (s : Size) -> String toString Small = “Small” toString Medium = “Medium” toString Large = “Large”

Our function now has multiple equations, each representing a different pattern in the input. The same as in Dafny!

Here we demonstrate the case expression, which is analogous to Dafny’s ‘match’.

toStringCase : (s : Size) -> String
toStringCase s
  = case s of
      Small => "Small"
      Medium => "Medium"
      Large => "Large"

The keyword case indicates we are matching on what s could be. Where as Dafny used ‘case’ to indicate each case, we use ‘case’ where Dafny used ‘match’.

Unlike Dafny, we do not need to delineate each possible ‘case’ with the keyword case. We do, however, use the ‘fat’ arrow to show what follows.

As with Dafny, if we do not want to examine any more cases we can use the wildcard pattern: _. Wildcards also work in equations

For example:

toStringCaseWild : (s : Size) -> String
toStringCaseWild s
  = case s of
      Small => "Small"
      _     => "Do not care"

and

toStringCaseWild' : (s : Size) -> String
toStringCaseWild' _ = "Do not care"

(Dependent) Pattern Matching

Within Idris, with constructs are a way of doing pattern matching in Idris when dealing with dependent data. We will explain more in Week 3 but show the syntax now.

If fact case and with are the same, all things considered,

The difference is that with is not an expression, and with shows us the LHS patterns in greater detail.

toStringDep : (s : Size) -> String
toStringDep s with (s)
  toStringDep s | Small  = "Samall"
  toStringDep s | Medium = "Medium"
  toStringDep s | Large  = "Large"

Coda

We will see a more foundational way to represent Enumerations when we discuss unions. Within Dafny and Idris, enumerations are tagged instances of the unit type!