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 ...
A Programming Classic!
Design
Patterns

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
A Programming Classic!
Design
Patterns

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