Published 2019-09-22.
Time to read: 2 minutes.
This lecture discusses sum types, which are a kind of algebraic data type (ADT). Using ADTs in your code can make it more readable and more error-free by defining an ADT subtype for every possible state or discrete value of a variable.
The sample code for this lecture can be found in
courseNotes/
.
Sum types are a kind of algebraic data type (ADT).
In Scala, sum types are formed by defining multiple types that extend a sealed trait.
This allows match expressions to be written that are guaranteed to handle all of the subtypes of the sealed trait.
I’ll show you why this is useful in a moment.
Before we explore that, you need to know that if the definition of a trait is prefixed with the sealed
keyword,
all subtypes of that trait must be defined in the same file as the trait.
Let’s look at an example.
sealed trait Index class LecturesIndex extends Index class SectionsIndex extends Index class CoursesIndex extends Index class GroupsIndex extends Index class SitesIndex extends Index
Now we can use a match
statement that operates on an instance of Index
.
The Scala compiler will issue a warning if one of the Index subtypes is not handled.
def showEm(index: Index): Index = index match { case si: SitesIndex => println(si) si
case gi: GroupsIndex => println(gi) gi
case ci: CoursesIndex => println(ci) ci
case si: SectionsIndex => println(si) si
case li: LecturesIndex => println(li) li }
Note that each match case in the above code block returns the Index subtype instance.
This allows the showEm
method to be chained with other methods provided by the Index
subtypes.
Here are some example of how the showEm
method could be invoked.
showEm(new LecturesIndex) showEm(new SectionsIndex)
You can run this code by typing:
$ sbt "runMain ADT1"
Return Values From Base Trait Methods
To define a method that is common to all of the types in an ADT, define the method in the base trait. For example, I’ve modified the Index base trait to define a method called show that prints out the value of the type (or subtype) and returns the valu.
sealed trait Index { def show(): Index = { println(this) this } }
Lets also enhance the LecturesIndex
and SectionsIndex
classes to define methods
lectureMethod1
and sectionMethod1
, respectively:
class LecturesIndex extends Index { def lectureMethod1: LecturesIndex = { println("LectureMethod1") this } }
class SectionsIndex extends Index { def sectionMethod1: SectionsIndex = { println("SectionMethod1") this } }
Note that even though every method return the subtype that defines it, for example SitesIndex
or GroupsIndex
, the Index
trait declares the return type of the show
method to be Index
, which limits the usefulness of the returned value unless it is cast to the desired subtype.
This type of casting can introduce errors and is considered bad practice.
new LecturesIndex().show().asInstanceOf[LecturesIndex].lectureMethod1 new SectionsIndex().show().asInstanceOf[SectionsIndex].sectionMethod1
showEm(new LecturesIndex).asInstanceOf[LecturesIndex].lectureMethod1 showEm(new SectionsIndex).asInstanceOf[SectionsIndex].sectionMethod1
You can run this code by typing:
$ sbt "runMain ADT2"
this.type
If the Index.show
method declares the return type to be this.type
instead of Index
,
then the appropriate Index
subtype is returned.
sealed trait Index {
def show(): this.type = {
println(this)
this
}
}
Now we can chain the show
methods with other methods without casting:
new LecturesIndex().show().lectureMethod1 new SectionsIndex().show().sectionMethod1
Note that the match expression’s return type is still Index
because that is the common supertype of all the return types from each case.
You can run this code by typing:
$ sbt "runMain ADT3"
© Copyright 1994-2024 Michael Slinn. All rights reserved.
If you would like to request to use this copyright-protected work in any manner,
please send an email.
This website was made using Jekyll and Mike Slinn’s Jekyll Plugins.