How to Create a Scala Sequence Class to be Used in a ‘for’ Expression

The best way I know to demonstrate how the Scala for expression works is for us to build our own collection class.

To keep things simple I’m going to create a custom class as a “wrapper” around an existing Scala collection class. The reason for this is that I want you to focus on the effects that writing map, flatMap, withFilter, and foreach methods have on how the class works in a for expression — not on writing the gory internals of a collection class.

A Sequence class

I always like to “begin with the end in mind” and picture how I want to use a class before I create it — i.e., its API — and to that end, this is how I want to use a custom class that I’m going to name Sequence:

val strings = Sequence("one", "two")
val nums = Sequence(1, 2, 3, 4, 5)
val peeps = Sequence(
    Person("Bert"),
    Person("Ernie"),
    Person("Grover")
)

From that code you can see that Sequence will be able to work with generic data types: it will be able to contain a series of String, Int, Person, and other data types.

Given those lines of code, you can infer some initial requirements about the Sequence class:

  • Sequence either needs to be a case class, or it needs to have a companion object that implements an apply method, because I want to be able to create new Sequence instances without needing the new keyword.
  • Because the class will be used as a container for generic elements, I’ll define the class to take a generic type.
  • Because Sequence instances can be created with a variable number of initial elements, the Sequence class constructor will be defined to accept a “varargs” parameter.

I’ll create this class in a series of steps over the next several lessons.

Create a case class named Sequence

The first step is to create a class named Sequence. I’m going to make it a case class so I can write code like this:

val strings = Sequence(1, 2, 3)

If I didn’t use a case class (or an apply method in a companion object) I’d have to write “new Sequence”, like this:

val strings = new Sequence("one", "two")

Therefore, I start by creating a case class named Sequence:

case class Sequence ...

Sequence will be a container for generic elements

Next, I know that I want Sequence to contain elements of different types, so I expand that definition to say that Sequence will be a container of generic types:

case class Sequence[A] ...

Sequence’s constructor will take a variable number of input parameters

Next, I know that the Sequence constructor will have one parameter, and that parameter can be assigned to a variable number of elements, so I expand the definition to this:

case class Sequence[A](initialElems: A*) ...

If you’re not familiar with using a varargs parameter, the * after the A is what lets you pass a variable number of elements into the Sequence constructor:

val a = Sequence(1,2)
val b = Sequence(1,2,3)
val c = Sequence('a', 'b', 'c', 'd', 'e')

Later in the Sequence class code you’ll see how to handle a variable number of input elements.

If you have a hard time with generics

If you have a hard time using generic types, it can help to remove the generic type A and use Int instead:

case class Sequence(initialElems: Int*) ...

With this code you only have to think about Sequence being a container for integers, so combined with the varargs constructor parameter, new instances of Sequence can be created like this:

val a = Sequence(1,2)
val b = Sequence(3,5,7,11,13,17,23)

Feel free to write your own code using Int rather than A, though I’ll use A in the rest of this lesson:

case class Sequence[A](initialElems: A*) ...

Sequence will be backed by a Scala collection class

As I mentioned at the beginning of this lesson, to keep this code from getting too complicated I’m going to implement Sequence as a wrapper around a Scala collection class. I originally wrote this lesson using a custom linked list class, but with that approach there was a lot of code that was unrelated to for expressions, so I opted to take this simpler approach.

The following code shows what I have in mind:

case class Sequence[A](initialElems: A*) {

    // this is a book, don't do this at home
    private val elems = scala.collection.mutable.ArrayBuffer[A]()

    // initialize
    elems ++= initialElems

}

I make ArrayBuffer private in this code so no consumers of my class can see how it’s implemented.

Scala constructors

If you haven’t use a Scala constructor in a while, remember that everything inside the body of a class that isn’t a method is executed when a new instance of the class is created. Therefore, this line of code:

elems ++= initialElems

is executed when a new class instance is created. For example, when you create a new Sequence like this:

val ints = Sequence(1, 2, 3)

the int values 1, 2, and 3 are added to the elems variable when the class is first created.

If you need to brush up on how Scala class constructors work, see Chapter 4 of the Scala Cookbook.

About ++=

If you’re not used to the ++= method, this line of code:

elems ++= initialElems

works just like this for loop:

for {
    e <- initialElems
} elems += e

Summary

If you want to test this code before moving on to the next lesson, paste it into the Scala REPL and then create new sequences like these:

val strings = Sequence("a", "b", "c")
val nums = Sequence(1, 2, 3, 4, 5)

you’ll see that they’re created properly:

scala> val strings = Sequence("a", "b", "c")
strings: Sequence[String] = Sequence(WrappedArray(a, b, c))

scala> val nums = Sequence(1, 2, 3, 4, 5)
nums: Sequence[Int] = Sequence(WrappedArray(1, 2, 3, 4, 5))

Notice that strings has the type Sequence[String], and nums has the type Sequence[Int]. That’s generics at work.

Don’t worry about the data on the right side of the = showing Sequence(WrappedArray(...)). That’s just an artifact of taking a varargs constructor parameter.

What’s next

Now that I have a simple Sequence class to work with, I’ll start to make it work with for expressions in the next lesson.

See also

books by alvin