This is the first in a series of articles working though the exercises in Brian Marick’s Functional Programming for the Object-Oriented Programmer, but implemented in Scala instead of Clojure. (Hat tip to Mr. Avdi Grimm for the idea.) The full source code is available on GitHub.
For each exercise, I first wrote a specification in ScalaTest and then wrote the implementation. I followed the recommendation of the ScalaTest user guide and created the UnitSpec base class that all of my specs extend:
1 2 3 4 | class UnitSpec extends FlatSpec with Matchers with OptionValues with Inside |
Exercise 1: second
First the test:
1 2 3 4 5 6 7 8 9 10 11 12 | class SecondSpec extends UnitSpec { "Exercise 1: second" should "return the second item in a given list" in { val list = List( "Lorem" , "ipsum" , "dolor" , "sit" , "amet" ) second(list) should be ( "ipsum" ) } it should "throw IndexOutOfBoundsException if called on a list with fewer than 2 elements" in { val listOf 1 = List( "sole" ) a [IndexOutOfBoundsException] should be thrownBy { second(listOf 1 ) } } } |
… and then the implementation—pretty simple:
1 2 3 | object Chapter 01 { def second[A](list : List[A]) : A = list( 1 ) } |
Exercise 2: third
Mr. Marick asked for two implementations. First the tests, then the implementations:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class ThirdSpec extends UnitSpec { "Exercise 2a: third" should "return the third item in a given list" in { val list = List( "Lorem" , "ipsum" , "dolor" , "sit" , "amet" ) third(list) should be ( "dolor" ) } it should "throw IndexOutOfBoundsException if called on a list with fewer than 3 elements" in { val listOf 2 = List( "penultimate" , "ultimate" ) a [IndexOutOfBoundsException] should be thrownBy { third(listOf 2 ) } } "Exercise 2b: third2" should "return the third item in a given list" in { val list = List( "Lorem" , "ipsum" , "dolor" , "sit" , "amet" ) third 2 (list) should be ( "dolor" ) } it should "throw NoSuchElementException if called on a list with fewer than 3 elements" in { val listOf 2 = List( "penultimate" , "ultimate" ) a [NoSuchElementException] should be thrownBy { third 2 (listOf 2 ) } } } |
Again, both are pretty simple:
1 2 3 4 | object Chapter 01 { def third[A](list : List[A]) : A = list( 2 ) def third 2 [A](list : List[A]) : A = list.tail.tail.head } |
Exercise 3: addSquares
The test for this one is pretty straightforward:
1 2 3 4 5 6 7 8 9 10 | class AddSquaresSpec extends UnitSpec { "Exercise 3: addSquares" should "square each item in a list and sum them" in { val list = List( 1 , 2 , 5 ) addSquares(list) should be ( 30 ) } it should "return 0 if called on an empty list" in { val emptyList = List[Int]() addSquares(emptyList) should be ( 0 ) } } |
I found the implementation somewhat challenging because I was not sure how to limit my input parameter to be a list of numbers. A second argument list that takes a single implicit parameter did the trick:
1 2 3 4 | object Chapter 01 { def addSquares[T](list : List[T])( implicit n : Numeric[T]) : T = list.map( x = > n.times(x, x) ).sum } |
Exercise 4: bizarreFactorial
In order to have a list of several inputs and the expected result for each, I used ScalaTest’s TableDrivenPropertyChecks trait.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | class BizarreFactorialSpec extends UnitSpec with TableDrivenPropertyChecks { "Exercise 4: bizarreFactorial" should "compute the factorial, i.e., n!, of a given integer n >= 0" in { val factorials = Table( ( "n" , "factorial" ), ( 0 , 1 ), ( 1 , 1 ), ( 2 , 2 ), ( 3 , 6 ), ( 4 , 24 ), ( 5 , 120 ) ) forAll (factorials) { (n : Int, factorial : Int) = > whenever (n > = 0 ) { bizarreFactorial(n) should be (factorial) } } } } |
Mr. Marick’s constraints were to use apply
, but not to use either iteration or recursion. Scala has apply
, but its purpose is to allow you to use an object as if it were a function. Clojure’s apply
is effectively used more like the way Scala uses reduce
on collections. Consequently, I could have implemented bizarreFactorial
this way:
1 2 3 4 5 6 7 | object Chapter 01 { def bizarreFactorial(n : Int) : Int = n match { case x if x < 0 = > throw new IllegalArgumentException( "Factorial only works for positive integers" ) case 0 = > 1 case _ = > 1 to n reduce ( _ * _ ) } } |
But Scala has a shorthand for that: product
(like sum
in Exercise 3 above), which yields slightly more readable code.
1 2 3 4 5 6 7 | object Chapter 01 { def bizarreFactorial(n : Int) : Int = n match { case x if x < 0 = > throw new IllegalArgumentException( "Factorial only works for positive integers" ) case 0 = > 1 case _ = > 1 to n product } } |
Exercise 5: Other Functions
As this exercise required the demonstration of a handful of functions that Clojure already has defined, I was able to use the Scala analogues in most cases:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | class OtherFunctionsSpec extends UnitSpec { "Exercise 5a: take" should "create a new sequence of the first n elements of an existing sequence" in { 1 to 10 take 3 should be (List( 1 , 2 , 3 )) } "Exercise 5b: distinct" should "remove duplicates from an existing sequence" in { val dupes = Seq( 1 , 2 , 3 , 4 , 2 , 3 , 4 , 5 , 3 , 4 , 5 , 6 ) dupes.distinct should be ( 1 to 6 ) } "Exercise 5c: ++" should "concatenate two sequences together" in { val a = 1 to 3 val b = 4 to 6 a ++ b should be ( 1 to 6 ) } "Exercise 5d: fill" should "create a sequence containing n copies of the same value" in { Seq.fill( 5 )( 2 ) should be (Seq( 2 , 2 , 2 , 2 , 2 )) } "Exercise 5e: interleave" should "interleave the elements of two sequences together" in { val evens = 0 to 8 by 2 val odds = 1 to 9 by 2 evens interleave odds should be ( 0 to 9 ) } "Exercise 5f.i: drop" should "remove the first n items from the sequence" in { 1 to 10 drop 3 should be ( 4 to 10 ) } "Exercise 5f.ii: dropRight" should "remove the last n items from the sequence" in { 1 to 10 dropRight 3 should be ( 1 to 7 ) } "Exercise 5g: flatten" should "turn a sequence of sequences into a sequence containing all of the values of each subsequence" in { Seq( 1 to 3 , 4 to 6 , 7 to 9 ).flatten should be ( 1 to 9 ) } "Exercise 5h: grouped" should "yield an iterator that turns the given sequence into a sequence of subsequences, each n items long" in { ( 1 to 9 grouped 3 ).toSeq should be (Seq( 1 to 3 , 4 to 6 , 7 to 9 )) } "Exercise 5i: forall" should "test whether all items in a sequence meet a certain condition" in { 1 to 9 forall { _ < 10 } should be ( true ) } "Exercise 5j: filterNot" should "remove items meeting a certain criterion from a given sequence" in { 1 to 10 filterNot { _ % 3 == 0 } should be (Seq( 1 , 2 , 4 , 5 , 7 , 8 , 10 )) } } |
Nevertheless, Clojure has one function interleave
that Scala does not have out of the box, so I had to implement it myself as an extension method:
1 2 3 4 5 6 7 | object Chapter 01 { implicit class Ops[A]( val seq : Seq[A]) extends AnyVal { def interleave(that : Seq[A]) : Seq[A] = { (seq, that).zipped flatMap { Seq( _ , _ ) } } } } |
Exercise 6: prefixOf
1 2 3 4 5 | class PrefixOfSpec extends UnitSpec { "Exercise 6: prefixOf" should "test whether a sequence consists of the first few elements of another sequence" in { ( 1 to 3 ) prefixOf ( 1 to 10 ) should be ( true ) } } |
Scala already has startsWith
, but it seemed like cheating to use it to implement prefixOf
:
1 2 3 4 5 6 7 | object Chapter 01 { implicit class Ops[A]( val seq : Seq[A]) extends AnyVal { def prefixOf(that : Seq[A]) : Boolean = { that startsWith seq } } } |
So I used take
:
1 2 3 4 5 6 7 | object Chapter 01 { implicit class Ops[A]( val seq : Seq[A]) extends AnyVal { def prefixOf(that : Seq[A]) : Boolean = { (that take seq.length) == seq } } } |
Exercise 7: tails
I came up with three implementations.
1 2 3 4 5 6 7 | class TailsSpec extends UnitSpec { val seq = 1 to 4 val expected = Seq( 1 to 4 , 2 to 4 , 3 to 4 , 4 to 4 , 4 until 4 ) "Exercise 7a: tails1" should "return a sequence of successively smaller subsequences of the argument" in { seq.tails 1 should be (expected) } } |
Scala already has an implementation of tails
although it returns an iterator instead of a fully constructed sequence. Implementing the version of Mr. Marick’s tails
function was no harder than calling toSeq
:
1 2 3 4 5 | object Chapter 01 { implicit class Ops[A]( val seq : Seq[A]) extends AnyVal { def tails 1 : Seq[Seq[A]] = seq.tails.toSeq } } |
But again, that seems like cheating, so my second implementation was my own.
1 2 3 4 5 6 7 | class TailsSpec extends UnitSpec { val seq = 1 to 4 val expected = Seq( 1 to 4 , 2 to 4 , 3 to 4 , 4 to 4 , 4 until 4 ) "Exercise 7b: tails2" should "return a sequence of successively smaller subsequences of the argument" in { seq.tails 2 should be (expected) } } |
The solution that first occurred to me was to use pattern matching and recursion:
1 2 3 4 5 6 7 8 9 10 11 | object Chapter 01 { implicit class Ops[A]( val seq : Seq[A]) extends AnyVal { def tails 2 : Seq[Seq[A]] = { def tailn(s : Seq[A]) : Seq[Seq[A]] = s match { case Seq() = > Seq(s) case _ = > s + : tailn(s.tail) } tailn(seq) } } } |
A perfectly valid implementation, and yet I wanted one more.
1 2 3 4 5 6 7 | class TailsSpec extends UnitSpec { val seq = 1 to 4 val expected = Seq( 1 to 4 , 2 to 4 , 3 to 4 , 4 to 4 , 4 until 4 ) "Exercise 7c: tails3" should "return a sequence of successively smaller subsequences of the argument" in { seq.tails 3 should be (expected) } } |
I took a peek at Mr. Marick’s Clojure implementation. I wanted to do a Scala version of it:
1 2 3 4 5 6 7 8 9 | object Chapter 01 { implicit class Ops[A]( val seq : Seq[A]) extends AnyVal { def tails 3 : Seq[Seq[A]] = { val seqs = Seq.fill(seq.length + 1 )(seq) val nToDrop = 0 to seq.length (seqs, nToDrop).zipped map ( _ drop _ ) } } } |
Well, that’s all for Chapter 1. Look for my take on Chapter 2 to come soon. Chapters 2–5 cover embedding an object-oriented language in Clojure. Because Scala is a hybrid functional–object-oriented language, I didn’t really see the point of doing them in Scala. Therefore the next post will cover chapter 6.