Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

1) General Rules About Null and Option

Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 46

https://alvinalexander.

com/scala/scala-null-values-option-uninitialized-variables
https://alvinalexander.com/scala/best-practice-eliminate-null-values-from-code-scala-idioms

1) General rules about null and Option

We begin with the following general rules regarding the use of null values in
Scala code:

o Ban null from any of your code. Period.


o If you're using a Java library that returns null, convert the result to
a Scala Option.

One important rule when working with an Option:

o Never call the get method on an Option. Always access Options


using map or flatMap, the for expression, or pattern matching.

As you can infer by this statement, it's important to note that Scala collection
classes are created to work with Options. While using Option with constructs
like for comprehensions and match expressions are nice, an enormous benefit
of using Option is that they're well supported by the methods of the collection
classes.

2) Handling uninitialized Scala variables

There are times you will need to create uninitialized fields in a Scala class.
When this need arises, use one of these approaches:

o Assign a default to the field that isn't null, such as an empty list,
array, or vector.
o If the field can be accessed before its initialized, declare it as
an Option, with a default value of None.
3) Writing Scala methods

Rules for writing Scala methods:

o Never return null.
o If you feel like returning a null value from a method, declare that
the method will return an Option[T] (such as Option[String]), then
return Some[T] and None from the method.
Benefits

Following these “rules” will eventually lead to the following benefits for you
and your Scala code:

o You'll avoid null pointer exceptions (NPEs).


o Using Options and mapping functions eventually leads you to a
different style of programming. There's a transformative effect that
helps you learn a safer, functional style of programming.

Null– Its a Trait.


null– Its an instance of Null- Similar to Java null.

Nil– Represents an emptry List of anything of zero length. Its not that it refers
to nothing but it refers to List which has no contents.

Nothing is a Trait. Its a subtype of everything. But not superclass of anything.


There are no instances of Nothing.

None– Used to represent a sensible return value. Just to avoid null pointer
exception. Option has exactly 2 subclasses- Some and None. None signifies
no result from the method.

Unit– Type of method that doesn’t return a value of anys sort.

Note: Any is supertype of AnyRef and AnyVal. AnyRef is the supertype of all
the reference classes (like String, List, Iterable) in scala. AnyVal is the
supertype of all the value classes (like Int, Float, Double, Byte, Short..). Null is
a subtype of all the reference classes. null is its only instance. Nothing is
subtype of every other type i.e of reference and value classes.

null
Scala’s null is the same as in Java. Any reference type can be null, like Strings, Objects, or
your own classes. Also just like Java, value types like Ints can’t be null. Odds are you'll see
this one.
Null
Null is a trait whose only instance is null. It is a subtype of all reference types, but not of
value types. It purpose in existing is to make it so reference types can be assigned null and
value types can’t. Don't worry about this one.

Nothing
Nothing is a trait that is guaranteed to have zero instances. It is a subtype of all other types.
It has two main reasons for existing: to provide a return type for methods
that never return normally (i.e. a method that always throws an exception). The other
reason is to provide a type for Nil (explained below). Don't worry about this one.

Nil
Nil is just an empty list, exactly like the result of List(). It is of type List[Nothing]. And since
we know there are no instances of Nothing, we now have a list that is statically verifiable as
empty. Nice to have. Odds are you'll see this one.

None
None is the counterpart to Some, used when you’re using Scala’s Option class to help
avoid null references. If you’re not familiar with the idea of Option or Maybe, here’s
an introduction to Option. Odds are you'll see this one.

Unit
Unit in Scala is the equivalent of void in Java. It’s used in a function’s signature when that
function doesn’t return a value. Odds are you'll see this one.

scala> val str1:String="abc"


str1: String = abc

scala> val str2:String=null


str2: String = null

scala> Option(str1).getOrElse("XXX")
res0: String = abc
scala> Option(str2).getOrElse("XXX")
res1: String = XXX

Solution

David Pollak, author of the book Beginning Scala, offers a wonderfully simple


rule about nullvalues:

“Ban null from any of your code. Period.”

Although I’ve used null values in this book to make some examples easier, in


my own practice, I no longer use them. I just imagine that there is no such
thing as a null, and write my code in other ways.

There are several common situations where you may be tempted to


use nullvalues, so this recipe demonstrates how not to use null values in those
situations:

o When a var field in a class or method doesn’t have an initial default


value, initialize it with Option instead of null.
o When a method doesn’t produce the intended result, you may be
tempted to return null. Use an Option or Try instead.
o If you’re working with a Java library that returns null, convert it to
an Option, or something else.

Let’s look at each of these techniques.

Initialize var fields with Option, not null

Possibly the most tempting time to use a null value is when a field in a class or
method won’t be initialized immediately. For instance, imagine that you’re
writing code for the next great social network app. To encourage people to sign
up, during the registration process, the only information you ask for is an
email address and a password. Because everything else is initially optional,
you might write some code like this:

case class Address (city: String, state: String, zip: String)

class User(email: String, password: String) {

var firstName: String = _

var lastName: String = _

var address: Address = _

This is bad news, because firstName, lastName, and address are all declared to


be null, and can cause problems in your application if they’re not assigned
before they’re accessed.

A better approach is to define each field as an Option:

case class Address (city: String, state: String, zip: String)

class User(email: String, password: String) {

var firstName = None: Option[String]

var lastName = None: Option[String]


var address = None: Option[Address]

Now you can create a User like this:

val u = new User("al@example.com", "secret")

At some point later you can assign the other values like this:

u.firstName = Some("Al")

u.lastName = Some("Alexander")

u.address = Some(Address("Talkeetna", "AK", "99676"))

Later in your code, you can access the fields like this:

println(firstName.getOrElse("<not assigned>"))

Or this:

u.address.foreach { a =>
println(a.city)

println(a.state)

println(a.zip)

In both cases, if the values are assigned, they’ll be printed. With the example
of printing the firstName field, if the value isn’t assigned, the string <not
assigned> is printed. In the case of the address, if it’s not assigned,
the foreach loop won’t be executed, so the print statements are never reached.
This is because an Option can be thought of as a collection with zero or one
elements. If the value is None, it has zero elements, and if it is a Some, it has one
element — the value it contains.

On a related note, you should also use an Option in a constructor when a field
is optional:

case class Stock (

id: Long,

var symbol: String,

var company: Option[String]

Don’t return null from methods


Because you should never use null in your code, the rule for
returning nullvalues from methods is easy: don’t do it.

This brings up the question, “If you can’t return null, what can you do?”

Answer: Return an Option. Or, if you need to know about an error that may
have occurred in the method, use Try instead of Option.

With an Option, your method signatures should look like this:

def doSomething: Option[String] = { ... }

def toInt(s: String): Option[Int] = { ... }

def lookupPerson(name: String): Option[Person] = { ... }

For instance, when reading a file, a method could return null if the process
fails, but this code shows how to read a file and return an Option instead:

def readTextFile(filename: String): Option[List[String]] = {

try {

Some(io.Source.fromFile(filename).getLines.toList)

} catch {

case e: Exception => None

}
This method returns a List[String] wrapped in a Some if the file can be found
and read, or None if an exception occurs.

As mentioned, if you want the error information instead of a Some or None, use


the Try/Success/Failure approach instead:

import scala.util.{Try, Success, Failure}

object Test extends App {

def readTextFile(filename: String): Try[List[String]] = {

Try(io.Source.fromFile(filename).getLines.toList)

val filename = "/etc/passwd"

readTextFile(filename) match {

case Success(lines) => lines.foreach(println)

case Failure(f) => println(f)

This code prints the lines from the /etc/passwd file if the code succeeds, or
prints an error message like this if the code fails:
java.io.FileNotFoundException: Foo.bar (No such file or directory)

The “Null Object” Pattern

As a word of caution (and balance), the Twitter Effective Scala page


recommends not overusing Option, and using the Null Object Pattern where it
makes sense. As usual, use your own judgment, but try to eliminate
all null values using one of these approaches.

A Null Object is an object that extends a base type with a “null” or neutral
behavior. Here’s a Scala implementation of Wikipedia’s Java example of a Null
Object:

trait Animal {

def makeSound()

class Dog extends Animal {

def makeSound() { println("woof") }

class NullAnimal extends Animal {

def makeSound() {}
}

The makeSound method in the NullAnimal class has a neutral, “do nothing”


behavior. Using this approach, a method defined to return an Animal can
return NullAnimal rather than null.

This is arguably similar to returning None from a method declared to return


an Option, especially when the result is used in a foreach loop.

Converting a null into an Option, or something else

The third major place you’ll run into null values is in working with legacy Java
code. There is no magic formula here, other than to capture the nullvalue and
return something else from your code. That may be an Option, a Null Object,
an empty list, or whatever else is appropriate for the problem at hand.

For instance, the following getName method converts a result from a Java


method that may be null and returns an Option[String] instead:

def getName: Option[String] = {

var name = javaPerson.getName

if (name == null) None else Some(name)

}
Currying – decomposition of function with multiple arguments into a chain of
single-argument functions.
Notice, that Scala allows to pass a function as an argument to another
function.

Partial application of function – pass to function less arguments than it has in


its declaration. Scala does not throw an exception when you provide less
arguments to function, it simply applies them and return a new function with
rest of arguments which need to be passed.

Currying examples
Partial application examples
def isInRange(left: Int, n: Int, right: Int): Boolean = {
    if (left < n && n < right) true else false
}
 
def is5InRange = isInRange(_: Int, 5, _: Int)
//(Int, Int) => Boolean
 
is5InRange(0, 8)
//true
 
def between0and10 = isInRange(0, _: Int, 10)
//Int => Boolean
 
between0and10(5)
//true
 
between0and10(100)
//false
https://stackoverflow.com/questions/18468786/understand-how-to-use-apply-and-unapply

https://theburningmonk.com/2017/01/from-f-to-scala-extractors/

The  apply function
In Scala, if you assign a function to a value, that value will have the type Function1[TInput,
TOutput]. Since everything in Scala is an object, this value also have a couple of functions on
it.
You can use andThen or compose to compose it with another function (think of them as
F#‘s » and « operators respectively).
The apply function applies the argument to the function, but you can invoke the function
without it.

Ok, now that we know what apply function’s role is, let’s go back to object.
If you declare an apply function in an object, it essentially allows the object to be used as a
factory class (indeed this is called the Factory pattern in Scala).

You see this pattern in Scala very often, and there are some useful built-in factories such
as Option (which wraps an object as Some(x) unless it’s null, in which case returns None).
String and BigInt defines their own apply function too (in String’s case, it returns the char at
the specified index) .

You can also define an apply function on a class as well as an object, and it works the same
way. For instance…

I find this notion of applying arguments to an object somewhat alien, almost as if this is an
elaborate way of creating a delegate even though Scala already have first-class functions…

Ok, can you pass multiple arguments to apply? What about overloading?

Check, and Check.

What about case classes and case object?


Check, and Check.

Ok. Can the apply function(s) be inherited and overridden like a normal function?

Check, and Check. Although this is consistent with inheritance and OOP in Java, I can’t help
but to feel it has the potential to create ambiguity and one should just stick with plain old
functions.

The  unapply function (aka extractors)


When you create a case class or case object, you also create a pattern that can be used in
pattern matching. It’s not the only way to create patterns in Scala, you can also create a
pattern by defining an unapply function in your class/object.
or, if you don’t want to return anything from the pattern.

So, the unapply function turns a Scala object into a pattern, and here are some limitations on


the unapply function:
1. it can only take one argument
2. if you want to return a value, then the return type T must defines members:
 isEmpty: Boolean
 get: Any

side note: point 2 is interesting. Looking at all the examples on the Internet one
might assume the unapply function must return an Option[T], but turns out it’s OK to return
any type so long it has the necessary members!
Whilst I can’t think of a situation where I’d need to use anything other than an Option[T],
this insight gives me a better understanding of how pattern matching in Scala works.
Whether or not the pattern matches is determined by the value of isEmpty of the result
type T. And the value returned by your pattern — ie msg in the example above — is
determined by the value of get of the result type T. So if you’re feeling a bit cheeky, you can
always do something like this:
Since the unapply function is a member on an object (like the apply function), it means it
should work with a class too, and indeed it does.

As you can see from the snippet above, this allows you to create parameterized
patterns and work around the limitation of having only one argument
in the unapply function.
You can nest patterns together too, for example.
Here, the Int pattern returns an Int, and instead of binding it to a name we can apply
another pattern inline to check if the value is even.
And whilst it doesn’t get mentioned in any of the articles I have seen, these patterns are not
limited to the match clause either. For instance, you can use it as part of declaration. (but be
careful, as you’ll get a MatchError if the pattern doesn’t match!)

https://docs.scala-lang.org/overviews/collections/concrete-mutable-collection-classes.html
Array Buffers
An ArrayBuffer buffer holds an array and a size. Most operations on an array buffer have
the same speed as for an array, because the operations simply access and modify the
underlying array. Additionally, array buffers can have data efficiently added to the end.
Appending an item to an array buffer takes amortized constant time. Thus, array buffers
are useful for efficiently building up a large collection whenever the new items are
always added to the end.

scala> val buf = scala.collection.mutable.ArrayBuffer.empty[Int]

buf: scala.collection.mutable. ArrayBuffer[Int] = ArrayBuffer()

scala> buf += 1

res32: buf. type = ArrayBuffer(1)

scala> buf += 10

res33: buf. type = ArrayBuffer(1, 10)

scala> buf.toArray
res34: Array[Int] = Array(1, 10)

List Buffers
A ListBuffer is like an array buffer except that it uses a linked list internally instead of an
array. If you plan to convert the buffer to a list once it is built up, use a list buffer instead
of an array buffer.

scala> val buf = scala.collection.mutable.ListBuffer.empty[Int]

buf: scala.collection.mutable. ListBuffer[Int] = ListBuffer()

scala> buf += 1

res35: buf. type = ListBuffer(1)

scala> buf += 10

res36: buf. type = ListBuffer(1, 10)

scala> buf.toList
res37: List[Int] = List(1, 10)

StringBuilders
Just like an array buffer is useful for building arrays, and a list buffer is useful for building
lists, a StringBuilder is useful for building strings. String builders are so commonly used
that they are already imported into the default namespace. Create them with a
simple new StringBuilder, like this:

scala> val buf = new StringBuilder

buf: StringBuilder =

scala> buf += 'a'

res38: buf. type = a

scala> buf ++= "bcdef"

res39: buf. type = abcdef

scala> buf.toString
res41: String = abcdef

Linked Lists
Linked lists are mutable sequences that consist of nodes which are linked with next
pointers. They are supported by class LinkedList. In most languages null would be picked
as the empty linked list. That does not work for Scala collections, because even empty
sequences must support all sequence methods. In
particular LinkedList.empty.isEmpty should return true and not throw
a NullPointerException. Empty linked lists are encoded instead in a special way:
Their next field points back to the node itself. Like their immutable cousins, linked lists
are best traversed sequentially. In addition linked lists make it easy to insert an element
or linked list into another linked list.

Double Linked Lists


Double linked lists are like single linked lists, except that they have besides nextanother
mutable field prev that points to the element preceding the current node. The main
benefit of that additional link is that it makes element removal very fast. Double linked
lists are supported by class DoubleLinkedList.

Mutable Lists
A MutableList consists of a single linked list together with a pointer that refers to the
terminal empty node of that list. This makes list append a constant time operation
because it avoids having to traverse the list in search for its terminal node. MutableList is
currently the standard implementation of mutable.LinearSeqin Scala.

Queues
Scala provides mutable queues in addition to immutable ones. You use a mQueuesimilarly
to how you use an immutable one, but instead of enqueue, you use the += and +
+= operators to append. Also, on a mutable queue, the dequeuemethod will just remove
the head element from the queue and return it. Here’s an example:

scala> val queue = new scala.collection.mutable.Queue[String]


queue: scala.collection.mutable. Queue[String] = Queue()

scala> queue += "a"

res10: queue. type = Queue(a)

scala> queue ++= List("b", "c")

res11: queue. type = Queue(a, b, c)

scala> queue

res12: scala.collection.mutable. Queue[String] = Queue(a, b, c)

scala> queue.dequeue

res13: String = a
scala> queue

res14: scala.collection.mutable. Queue[String] = Queue(b, c)

Array Sequences
Array sequences are mutable sequences of fixed size which store their elements
internally in an Array[Object]. They are implemented in Scala by class ArraySeq.
You would typically use an ArraySeq if you want an array for its performance
characteristics, but you also want to create generic instances of the sequence where you
do not know the type of the elements and you do not have a ClassTag to provide it at
run-time. These issues are explained in the section on arrays.

Stacks
You saw immutable stacks earlier. There is also a mutable version, supported by
class mutable.Stack. It works exactly the same as the immutable version except that
modifications happen in place.

scala> val stack = new scala.collection.mutable.Stack[Int]

stack: scala.collection.mutable. Stack[Int] = Stack()

scala> stack.push( 1)

res0: stack. type = Stack(1)


scala> stack

res1: scala.collection.mutable. Stack[Int] = Stack(1)

scala> stack.push( 2)

res0: stack. type = Stack(1, 2)

scala> stack

res3: scala.collection.mutable. Stack[Int] = Stack(1, 2)

scala> stack.top

res8: Int = 2

scala> stack
res9: scala.collection.mutable. Stack[Int] = Stack(1, 2)

scala> stack.pop

res10: Int = 2

scala> stack

res11: scala.collection.mutable. Stack[Int] = Stack(1)

Array Stacks
ArrayStack is an alternative implementation of a mutable stack which is backed by an
Array that gets re-sized as needed. It provides fast indexing and is generally slightly more
efficient for most operations than a normal mutable stack.

Hash Tables
A hash table stores its elements in an underlying array, placing each item at a position in
the array determined by the hash code of that item. Adding an element to a hash table
takes only constant time, so long as there isn’t already another element in the array that
has the same hash code. Hash tables are thus very fast so long as the objects placed in
them have a good distribution of hash codes. As a result, the default mutable map and
set types in Scala are based on hash tables. You can access them also directly under the
names mutable.HashSet and mutable.HashMap.

Hash sets and maps are used just like any other set or map. Here are some simple
examples:
scala> val map = scala.collection.mutable.HashMap.empty[Int,String]

map: scala.collection.mutable. HashMap[Int,String] = Map()

scala> map += ( 1 -> "make a web site")

res42: map. type = Map(1 -> make a web site)

scala> map += ( 3 -> "profit!")

res43: map. type = Map(1 -> make a web site, 3 -> profit!)

scala> map( 1)

res44: String = make a web site

scala> map contains 2


res46: Boolean = false

Iteration over a hash table is not guaranteed to occur in any particular order. Iteration
simply proceeds through the underlying array in whichever order it happens to be in. To
get a guaranteed iteration order, use a linked hash map or set instead of a regular one. A
linked hash map or set is just like a regular hash map or set except that it also includes a
linked list of the elements in the order they were added. Iteration over such a collection
is always in the same order that the elements were initially added.

Weak Hash Maps


A weak hash map is a special kind of hash map where the garbage collector does not
follow links from the map to the keys stored in it. This means that a key and its
associated value will disappear from the map if there is no other reference to that key.
Weak hash maps are useful for tasks such as caching, where you want to re-use an
expensive function’s result if the function is called again on the same key. If keys and
function results are stored in a regular hash map, the map could grow without bounds,
and no key would ever become garbage. Using a weak hash map avoids this problem. As
soon as a key object becomes unreachable, it’s entry is removed from the weak
hashmap. Weak hash maps in Scala are implemented by class WeakHashMap as a
wrapper of an underlying Java implementation java.util.WeakHashMap.

Concurrent Maps
A concurrent map can be accessed by several threads at once. In addition to the
usual Map operations, it provides the following atomic operations:

Operations in class ConcurrentMap

WHAT IT IS WHAT IT DOES

m putIfAbsent(k, v) Adds key/value binding k -> v unless k is already defined in m

m remove (k, v) Removes entry for k if it is currently mapped to v.

m replace (k, old, Replaces value associated with key k to new, if it was previously
new) bound to old.
WHAT IT IS WHAT IT DOES

Replaces value associated with key k to v, if it was previously


m replace (k, v)
bound to some value.

ConcurrentMap is a trait in the Scala collections library. Currently, its only implementation
is Java’s java.util.concurrent.ConcurrentMap, which can be converted automatically into
a Scala map using the standard Java/Scala collection conversions.

Mutable Bitsets
A mutable bit of type mutable.BitSet set is just like an immutable one, except that it is
modified in place. Mutable bit sets are slightly more efficient at updating than immutable
ones, because they don’t have to copy around Longs that haven’t changed.

scala> val bits = scala.collection.mutable.BitSet.empty

bits: scala.collection.mutable. BitSet = BitSet()

scala> bits += 1

res49: bits. type = BitSet(1)

scala> bits += 3

res50: bits. type = BitSet(1, 3)


scala> bits

res51: scala.collection.mutable. BitSet = BitSet(1, 3)


Collection:

https://learning.oreilly.com/learning-paths/learning-path-scala/9781789616002/9781788295567-
video2_6
Scala Enumerations
https://pedrorijo.com/blog/scala-enums/

E numerations are a language feature specially useful for modeling a finite


set of entities. A classical example is modeling the weekdays as an
enumeration: one value for each of the seven days. Scala, as many other
languages, provides a native way to represent enumerations:

object Weekday extends Enumeration {


val Monday, Tuesday, Wednesday, Thursday, Friday,
Saturday, Sunday = Value
}

Now we can safely represent weekdays without using primitive types


like String or Int. Scala Enumerations also provide a set of useful features:

 Serialize and Deserialize methods - unfortunately, it may throw an


exception :(

scala> Weekday.Monday.toString
res0: String = Monday

scala> Weekday.withName("Monday")
res1: Weekday.Value = Monday

scala> Weekday.withName("Mondai")
java.util.NoSuchElementException: No value found for
'Mondai'
at scala.Enumeration.withName(Enumeration.scala:124)
... 32 elided

 Provide an human-readable value

object Weekday extends Enumeration {


val Monday = Value("Mo.")
val Tuesday = Value("Tu.")
val Wednesday = Value("We.")
val Thursday = Value("Th.")
val Friday = Value("Fr.")
val Saturday = Value("Sa.")
val Sunday = Value("Su.")
}

scala> Weekday.Monday.toString
res0: String = Mo.

 Listing all possible values

scala> Weekday.values
res0: Weekday.ValueSet = Weekday.ValueSet(Monday,
Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday)

 Ordering. By default, enumeration values are ordered by the order they


are declared. This can be overridden

object Weekday extends Enumeration {


val Monday, Tuesday, Wednesday, Thursday, Friday,
Saturday, Sunday = Value
}

scala> Weekday.values.toList.sorted
res0: List[Weekday.Value] = List(Monday, Tuesday,
Wednesday, Thursday, Friday, Saturday, Sunday)
object Weekday extends Enumeration {
val Monday = Value(1)
val Tuesday = Value(2)
val Wednesday = Value(3)
val Thursday = Value(4)
val Friday = Value(5)
val Saturday = Value(6)
val Sunday = Value(0)
}

scala> Weekday.values.toList.sorted
res1: List[Weekday.Value] = List(Sunday, Monday, Tuesday,
Wednesday, Thursday, Friday, Saturday)
Problems with scala.Enumeration 
Nevertheless, this approach has some problems. There are two main
disadvantages:

 Enumerations have the same type after erasure.

object Weekday extends Enumeration {


val Monday, Tuesday, Wednesday, Thursday, Friday,
Saturday, Sunday = Value
}

object OtherEnum extends Enumeration {


val A, B, C = Value
}

def test(enum: Weekday.Value) = {


println(s"enum: $enum")
}

def test(enum: OtherEnum.Value) = {


println(s"enum: $enum")
}

<console>:25: error: double definition:


def test(enum: Weekday.Value): Unit at line 21 and
def test(enum: OtherEnum.Value): Unit at line 25
have same type after erasure: (enum:
Enumeration#Value)Unit
def test(enum: OtherEnum.Value) = {
^

 There’s no exhaustive matching check during compile. The following


example would compile without any warnings, but it would fail on
runtime with a scala.MatchError exception to weekdays other than
Monday and Sunday:

def nonExhaustive(weekday: Weekday.Value) {


weekday match {
case Monday => println("I hate Mondays")
case Sunday => println("The weekend is already
over? :( ")
}
}

In Scala we heavily rely on the compiler powerful type system, and with this
approach, the compiler is not able to find non exhaustive pattern matching
clauses, nor to use overloaded methods for different Enumerations.

In order to avoid such problems, other solutions have been developed:


 using sealed case objects
 itemized
 enumeratum

Sealed case objects


If you decide to use sealed case objects, the Scala compiler can solve both
existing problems in Scala enumerations. The compiler is able both to detect
non exhaustive pattern matching and also to avoid type erasure problems.

sealed trait Weekday

case object Monday extends Weekday


case object Tuesday extends Weekday
case object Wednesday extends Weekday
case object Thursday extends Weekday
case object Friday extends Weekday
case object Saturday extends Weekday
case object Sunday extends Weekday

def test(weekday: Weekday) = {


weekday match {
case Monday => println("I hate Mondays")
case Sunday => println("The weekend is already
over? :( ")
}
}

<console>:15: warning: match may not be exhaustive.


It would fail on the following inputs: Friday, Saturday,
Thursday, Tuesday, Wednesday
weekday match {
^
test: (weekday: Weekday)Unit

Another really nice feature, is the possibility to include more fields on the
enumeration values (Scala enumerations only provides an index and a name).
Just use a (sealed) abstract classinstead of a (sealed) trait.

sealed abstract class Weekday( val name: String,


val abbreviation: String,
val isWorkDay: Boolean)

case object Monday extends Weekday("Monday", "Mo.", true)


case object Tuesday extends Weekday("Tuesday", "Tu.",
true)
case object Wednesday extends Weekday("Wednesday", "We.",
true)
case object Thursday extends Weekday("Thursday", "Th.",
true)
case object Friday extends Weekday("Friday", "Fr.", true)
case object Saturday extends Weekday("Saturday", "Sa.",
false)
case object Sunday extends Weekday("Sunday", "Su.",
false)
Problems with sealed case objects 
But this approach has its own set of problems too:

 there is no simple way to retrieve all the enumeration values


 there are no default serialize/deserialize methods
 there is no default ordering between enumeration values - this can be
achieved manually by including some info on the values, check the
example:

sealed abstract class Weekday( val name: String,


val abbreviation: String,
val isWeekDay: Boolean,
val order: Int) extends
Ordered[Weekday] {

def compare(that: Weekday) = this.order - that.order


}

case object Monday extends Weekday("Monday", "Mo.", true,


2)
case object Tuesday extends Weekday("Tuesday", "Tu.",
true, 3)
case object Wednesday extends Weekday("Wednesday", "We.",
true, 4)
case object Thursday extends Weekday("Thursday", "Th.",
true, 5)
case object Friday extends Weekday("Friday", "Fr.", true,
6)
case object Saturday extends Weekday("Saturday", "Sa.",
false, 7)
case object Sunday extends Weekday("Sunday", "Su.",
false, 1)

scala> Monday < Tuesday


res0: Boolean = true

itemized
itemized is a OSS lib that’s part of rbricks, a collection of composable, small-
footprint libraries for Scala.

itemized provides macros and typeclasses for enums encoded as sealed trait


hierarchies - our previous example

import io.rbricks.itemized.annotation.enum

@enum trait Weekday {


object Monday
object Tuesday
object Wednesday
object Thursday
object Friday
object Saturday
object Sunday
}

but itemized comes with some additional features:

 list all enumeration values (WIP)


 default serialization/deserialization methods

scala> import io.rbricks.itemized.ItemizedCodec

scala> ItemizedCodec[Weekday].fromRep("Monday")
res0: Option[Weekday] = Some(Monday)

scala> val weekday: Weekday = Planet.Monday


scala> import io.rbricks.itemized.ItemizedCodec.ops._

scala> weekday.toRep
res1: String = Earth
Problems with itemized 
Although it makes easier to create type safe enumerations by using small
annotations, it also brings some disadvantages:

 there is no way to add more fields to enumeration values. Since part of


the work is made by the macro, at this point, there is no way to pass
those values
 although it provides indexed values, there’s still no default order on
enumeration values

enumeratum
Enumeratum is a type-safe and powerful enumeration implementation for
Scala that offers exhaustive pattern match warnings.

import enumeratum._

sealed trait Weekday extends EnumEntry


object Weekday extends Enum[Weekday] {
val values = findValues // mandatory due to Enum
extension

case object Monday extends Weekday


case object Tuesday extends Weekday
case object Wednesday extends Weekday
case object Thursday extends Weekday
case object Friday extends Weekday
case object Saturday extends Weekday
case object Sunday extends Weekday
}
def test(weekday: Weekday) = {
weekday match {
case Weekday.Monday => println("I hate Mondays")
case Weekday.Sunday => println("The weekend is
already over? :( ")
}
}

<console>:18: warning: match may not be exhaustive.


It would fail on the following inputs: Friday, Saturday,
Thursday, Tuesday, Wednesday
weekday match {
^
test: (weekday: Weekday)Unit

Besides nonExhaustive pattern matching warnings, enumeratum also


provides:

 listing possible values (because the value values needs to be


implemented on Enum inheritance)
 default serialization/deserialization methods (with and without
throwing exceptions)

scala> Weekday.withName("Monday")
res0: Weekday = Monday

scala> Weekday.withName("Momday")
java.util.NoSuchElementException: Momday is not a member
of Enum (Monday, Tuesday, Wednesday, Thursday, Friday,
Saturday, Sunday)
at enumeratum.Enum$
$anonfun$withName$1.apply(Enum.scala:82)
at enumeratum.Enum$
$anonfun$withName$1.apply(Enum.scala:82)
at scala.Option.getOrElse(Option.scala:121)
at enumeratum.Enum$class.withName(Enum.scala:81)
at Weekday$.withName(<console>:13)
... 43 elided

scala> Weekday.withNameOption("Monday")
res2: Option[Weekday] = Some(Monday)

scala> Weekday.withNameOption("Momday")
res3: Option[Weekday] = None
 Adding extra values to enumeration. It is very similar to how we could
add extra values with simple sealed case objects

sealed abstract class Weekday( val name: String,


val abbreviation: String,
val isWorkDay: Boolean)
extends EnumEntry

case object Weekday extends Enum[Weekday] {


val values = findValues
case object Monday extends Weekday("Monday", "Mo.",
true)
case object Tuesday extends Weekday("Tuesday", "Tu.",
true)
case object Wednesday extends Weekday("Wednesday",
"We.", true)
case object Thursday extends Weekday("Thursday", "Th.",
true)
case object Friday extends Weekday("Friday", "Fr.",
true)
case object Saturday extends Weekday("Saturday", "Sa.",
false)
case object Sunday extends Weekday("Sunday", "Su.",
false)
}

 Ordering can be achieved in the same way that we did in sealed


hierarchies. Just need to mixin with the Ordered[] trait and
implement the compare method.

sealed abstract class Weekday(val order: Int) extends


EnumEntry with Ordered[Weekday] {
def compare(that: Weekday) = this.order - that.order
}

object Weekday extends Enum[Weekday] {


val values = findValues

case object Monday extends Weekday(2)


case object Tuesday extends Weekday(3)
case object Wednesday extends Weekday(4)
case object Thursday extends Weekday(5)
case object Friday extends Weekday(6)
case object Saturday extends Weekday(7)
case object Sunday extends Weekday(1)
}

 Support for several libraries and frameworks that can be real useful on
bigger applications. Check the project documentation

Conclusion
If you are just starting into Scala I would recommend to use the native
approach with scala.Enumeration. As you feel more comfortable using
more Scala features, and as you start enjoying compiler safeties you will
probably want to migrate to something else. My two recommendations would
be:

 sealed hierarchies if you do not want to depend on external libraries


 enumeratum as it provides all the features referred here

Relevant features referred


 Exhaustive pattern matching
 No type erasure
 Default methods for (safe) serialization/deserialization
 List all possible values
 Add extra fields on enumeration values
 Ordering

Follow up
If you want to see more alternatives, check out the follow up on Scala
Enumerations - Return of the (Java) Jedi
https://docs.scala-lang.org/overviews/core/string-interpolation.html#the-raw-interpolator

You might also like