% Enums % % Enums: Motivation ================= We have seen that case classes *aggregate several values* into a single abstraction. For instance, the `Rational` case class aggregates a numerator and a denominator. Conversely, how could we define an abstraction *accepting alternative values*? \example Define a `Paradigm` type that can be either `Functional` or `Imperative`. Enums ===== We can define an abstraction accepting alternative values with an \red{enum}: ~~~ enum Paradigm case Functional, Imperative ~~~ This definition introduces: - A new \red{type}, named `Paradigm`. - Two possible \red{values} for this type, `Paradigm.Functional` and `Paradigm.Imperative`. Enumerate the Values of an Enumeration ====================================== It is possible to enumerate all the values of an enum by calling the `values` operation on the enum companion object: \begin{tabular}{ll} \verb@Paradigm.values@ \wsf Array(Functional, Imperative) \\ \verb@val p = Paradigm.Functional@ \wsf p: Paradigm = Functional \\ \verb@p == Paradigm.values(0)@ \wsf true \end{tabular} Discriminate the Values of an Enumeration ========================================= You can discriminate between the values of an enum by using a \red{match} expression: ~~~ def isReferentiallyTransparent(paradigm: Paradigm): Boolean = paradigm match case Paradigm.Functional => true case Paradigm.Imperative => false ~~~ -> And then: \begin{tabular}{ll} \verb@val p = Paradigm.Functional@ \wsf p: Paradigm = Functional \\ \verb@isReferentiallyTransparent(p)@ \wsf true \end{tabular} Match Syntax ============ _Pattern matching_ is a generalization of `switch` from C/Java to class hierarchies. It’s expressed in Scala using the `match` keyword. - `match` is followed by a sequence of \red{cases}, `case value => expr`. - Each case associates an \red{expression} `expr` with a \red{constant} `value`. We will see in the next slides that pattern matching can do more than discriminating enums. Enumerations Can Have Methods ============================= Alternatively, we can define `isReferentiallyTransparent` as a method: ~~~ enum Paradigm case Functional, Imperative def isReferentiallyTransparent: Boolean = this match case Functional => true case Imperative => false ~~~ -> And then: \begin{tabular}{ll} \verb@val p = Paradigm.Functional@ \wsf p: Paradigm = Functional \\ \verb@p.isReferentiallyTransparent@ \wsf true \end{tabular} Enumerations Values Can Take Parameters ======================================= Consider the following definition of a data type modeling students following a lecture: ~~~ enum Student case Listening case Asking(question: String) ~~~ This definition introduces a `Student` type consisting of two cases, `Listening` or `Asking`. The `Asking` case is parameterized with a value parameter `question`. Since `Listening` is not parameterized, it is treated as a normal enum value. Matching on Constructors ======================== Since `Asking` is not a constant, we match on it using a _constructor pattern_: ~~~ student match case Student.Listening => "Student is listening" case Student.Asking(q) => s"Student is asking: $q" ~~~ A constructor pattern allows us to _extract_ the value of the `question` parameter, in case the `student` value is indeed of type `Asking`. Here, the `q` identifier is bound to the `question` parameter of the `student` object. Evaluating Match Expressions ============================ An expression of the form $$\btt e\ match\ \{\ case\ p_1 => e_1\ ...\ case\ p_n => e_n\ \} $$ matches the value of the selector $\btt e$ with the patterns $\btt p_1, ..., p_n$ in the order in which they are written. The whole match expression is rewritten to the right-hand side of the first case where the pattern matches the selector $e$. References to pattern variables are replaced by the corresponding parts in the selector. Forms of Patterns ================= Patterns are constructed from: - _constructors_, e.g. `Rational`, `Student.Asking`, - _variables_, e.g. `n`, `e1`, `e2`, - _wildcard patterns_ `_`, - _constants_, e.g. `1`, `true`, `Paradigm.Functional`. Variables always begin with a _lowercase letter_. The same variable name can only appear _once_ in a pattern. Names of constants begin with a capital letter, with the exception of the reserved words `null`, `true`, `false`. What Do Patterns Match? ======================= - A constructor pattern $\btt C(p_1 , ..., p_n)$ matches all the values of type $\btt C$ (or a subtype) that have been constructed with arguments matching the patterns $\btt p_1, ..., p_n$. - A variable pattern $\btt x$ matches any value, and \red{binds} the name of the variable to this value. - A constant pattern $\btt c$ matches values that are equal to $\btt c$ (in the sense of `==`) Matching on Case Classes ======================== Constructor patterns also works with case classes: ~~~ def invert(x: Rational): Rational = x match case Rational(0, _) => sys.error("Unable to invert zero") case Rational(n, d) => Rational(d, n) ~~~ Relationship Between Case Classes and Parameterized Enum Cases ============================================================== Case classes and parameterized enum cases are very similar. When should you use one or the other? - Parameterized enum cases should be used to define a type that is part of a set of alternatives, - If there are no alternatives, just use a case class. Enumerations Can Be Recursive ============================= A list of integer values can be modeled as either an empty list, or a node containing both a number and a reference to the remainder of the list. ~~~ enum List case Empty case Node(value: Int, next: List) ~~~ A list with values `1`, `2`, and `3` can be constructed as follows: ~~~ List.Node(1, List.Node(2, List.Node(3, List.Empty))) ~~~ Enumerations Can Take Parameters ================================ ~~~ enum Vehicle(val numberOfWheels: Int) case Unicycle extends Vehicle(1) case Bicycle extends Vehicle(2) case Car extends Vehicle(4) ~~~ - Enumeration cases have to use an explicit `extends` clause Exercise: Arithmetic Expressions ================================ Define a datatype modeling arithmetic expressions. An expression can be: - a number (e.g. `42`), - a sum of two expressions, - or, the product of two expressions. `Expr` Type Definition ====================== ~~~ enum Expr case Number(n: Int) case Sum(lhs: Expr, rhs: Expr) case Prod(lhs: Expr, rhs: Expr) -> \begin{tabular}{ll} \verb@val one = Expr.Number(1)@ \wsf one: Expr = Number(1) \\ \verb@val two = Expr.Number(2)@ \wsf two: Expr = Number(2) \\ \verb@Expr.Sum(one, two)@ \wsf Sum(Number(1), Number(2)) \end{tabular} Exercise ======== Is the following match expression valid? ~~~ expr match case Expr.Sum(x, x) => Expr.Prod(2, x) case e => e ~~~ O Yes O No Exercise ======== Implement an `eval` operation that takes an `Expr` as parameter and evaluates its value: ~~~ def eval(e: Expr): Int ~~~ Examples of use: \begin{tabular}{ll} \verb@eval(Expr.Number(42))@ \wsf 42 \\ \verb@eval(Expr.Prod(2, Expr.Sum(Expr.Number(8), Expr.Number(13))))@ \wsf 42 \end{tabular} Exercise ======== Implement a `show` operation that takes an `Expr` as parameter and returns a `String` representation of the operation. Be careful to introduce parenthesis when necessary! ~~~ def show(e: Expr): String ~~~ Examples of use: \begin{tabular}{ll} \verb@show(Expr.Number(42))@ \wsf "42" \\ \verb@show(Expr.Prod(2, Expr.Sum(Expr.Number(8), Expr.Number(13))))@ \wsf "2 * (8 + 13)" \end{tabular} Summary ======= In this lecture, we have seen: - how to define data types accepting alternative values using `enum` definitions, - enumeration cases can be discriminated using pattern matching, - enumeration cases can take parameters or be simple values, - pattern matching allows us to extract information carried by an object.