Creative Scala
Creative Scala
Creative Scala
Crea ve Scala
Dave Gurnell and Noel Welsh
February 2015
underscore
Copyright 2014 Dave Gurnell and Noel Welsh.
Crea ve Scala
February 2015
Copyright 2014 Dave Gurnell and Noel Welsh.
Published by Underscore Consul ng LLP, Brighton, UK.
Our courses, workshops, and other products can help you and your team create be er so ware and have more fun. For
more informa on, as well as the latest Underscore tles, please visit h p://underscore.io/training.
Disclaimer: Every precau on was taken in the prepara on of this book. However, the author and Underscore Consul ng LLP
assume no responsibility for errors or omissions, or for damages that may result from the use of informa on (including program
lis ngs) contained herein.
Contents
Foreword
Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.1 Literals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
10
1.1.4 Operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
11
11
1.2 Images . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
12
1.2.2 Layout
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
1.2.3 Colour
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
16
1.3.1 Subs tu on
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
16
17
2 Declara ons
19
19
19
21
22
23
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
24
24
CONTENTS
27
27
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
30
32
35
4 Collec ons
37
37
38
42
5 Summary
43
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
5.2 Abstrac on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
5.3 Composi on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44
44
44
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
45
46
46
47
47
47
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
48
49
51
51
51
51
51
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
51
52
52
53
53
53
53
54
CONTENTS
54
54
55
57
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
CONTENTS
Foreword
Crea ve Scala is aimed at developers who have no prior experience in Scala. It is designed to give you a fun introduc on
to func onal programming. We assume you have some familiarity with another programming language but li le or no
experience with Scala or other func onal languages.
Our goal is to demonstrate the building blocks that Scala developers use to create programs in a clear, succinct, declara ve
manner. Working through the exercises in the book should take a few hours, a er which we hope you will have a feel of
what Scala can do for your applica ons.
Although this book will give you the basic mental model required to become competent with Scala, you wont nish
knowing everything you need to be self-sucient. For further advancement we recommend considering one of the many
excellent Scala textbooks out there, including our own Essen al Scala.
If you are working through the exercises on your own, we highly recommend joining our Gi er chat room to provide get
help with the exercises and provide feedback on the book.
The text of Crea ve Scala is open souce, as is the source code for the Doodle drawing library used in the exercises. You
can grab the code from our Github account. Contact us on Gi er or by email if you would like to contribute.
Thanks for downloading and happy crea ve programming!
Dave and Noel
This is an early access release of Crea ve Scala. This means there are unnished aspects as detailed below. There
may be typos and errata in the text and examples.
If you spot any mistakes or would like to provide feedback, please let us know via our Gi er chat room or by email:
Dave Gurnell (dave@underscore.io)
Noel Welsh (noel@underscore.io)
Acknowledgements
Crea ve Scala was wri en by Dave Gurnell and Noel Welsh. Many thanks to Richard Dallaway, Jonathan Ferguson, and
the team at Underscore for their invaluable contribu ons and extensive proof reading.
CONTENTS
Chapter 1
A Scala program goes through two dis nct stages. First it is compiled; if compiles successfully it can then be executed. The
most important dis nc on between types and values is that types are determined at compile me, whereas values can
only be determined at run me. Values can change each me we run the code, whereas types are xed. For example, the
following expression is certainly of type String, but its value depends on the the user input each me it is run:
scala> readLine.toUpperCase
We are used to thinking of types that refer to sets of literals such as Int, Boolean, and String, but in general a type is
dened as anything we can infer about a program without running it. Scala developer use types to gain assurances about our
programs before we put them into produc on.
1.1
Lets look at some of the basic kinds of expressions we can write in Scala:
1.1.1
Literals
The simplest kinds of expressions are literals. These are fragments of code that stand for themselves. Scala supports a
similar set of literals to Java:
// Integers:
scala> 1
res0: Int = 1
// Floating point numbers:
scala> 0.1
res1: Double = 0.1
// Booleans:
scala> true
res2: Boolean = true
10
// Strings:
scala> "Hello world!"
res3: String = Hello world!
// And so on...
1.1.2
Method Calls
Scala is a completely object-oriented programming language, so all values are objects with methods that we can call.
Method calls are another type of expression:
scala> 123.4.ceil
res4: Double = 124.0
scala> true.toString
res5: String = "true"
1.1.3
Constructor Calls
Scala only has literals for a small set of types (Int, Boolean, String, and so on). To create values of other types we either
need to call a method, or we need to call a constructor using the new keyword. This behaves similarly to new in Java:
scala> import java.util.Date
import java.util.Date
scala> new Date()
res4: java.util.Date = Tue Feb 10 10:30:21 GMT 2015
The new operator tends to be a distrac on when wri ng larger, more complex expressions. For this reason, Scala libraries
typically provide factory methods to wrap constructor calls. The eec ve dierence is that we can create many Scala data
types without wri ng new:
scala> List(1, 2, 3)
res6: List[Int] = List(1, 2, 3)
Other than the lack of a new keyword, the seman cs here are similar to the Date example above:
1.1.4
Operators
Operators in Scala are actually method calls under the hood. Scala has a set of convenient syntac c shorthands to allow
us to write normal-looking code without excessive numbers of parentheses. The most common of these is inx syntax,
which allows us to write any expression of the form a.b(c) as a b c, without the full stop or parentheses:
scala> 1 .+(2).+(3).+(4) // the space prevents `1` being treated as a double
res0: Int = 10
scala> 1 + 2 + 3 + 4
res1: Int = 10
1.1.5
11
Condi onals
Many other syntac c constructs are expressions in Scala, including some that are statements in Java. Condi onals (if
expressions) are a great example:
// Conditionals ("if expressions"):
scala> if(123 > 456) "Higher!" else "Lower!"
res6: String = Lower!
1.1.6
Blocks are another type of expression in Scala. Running a block runs each contained expression in order. The type and
return value of the block are determined by the last contained expression:
scala> {
|
println("First line")
println("Second line")
1 + 2 + 3
| }
res0: Int = 6
In func onal programming we make the dis c on between pure expressions and expressions that have side eects:
pure expressions do nothing more than calculate a value;
expressions with side eects do something else aside from calculate their resultfor example, println prints a
message to the console.
Because the results of intermediate expressions in a block are thrown away, it doesnt make sense to use pure expressions
there. The Scala console even warns us when we try this:
scala> {
|
1 + 2 + 3
4 + 5 + 6
| }
<console>:9: warning: a pure expression does nothing in statement position;
you may be omitting necessary parentheses
1 + 2 + 3
^
res0: Int = 15
The message here is warning us that the intermediate expression 1 + 2 + 3 is wasted computa on. All it does is calculate
the value 6. We immediately throw the result away and calculate 4 + 5 + 6 instead. We might as well simply write 4 +
5 + 6 and get rid of the block.
Side-eec ng expressions, by contrast, make perfect sense within a block. println expressions are a great example of
thisthey do something useful even though they dont return a useful value:
scala> {
|
4 + 5 + 6
| }
Intermediate result: 6
res0: Int = 15
Scala developers tend to prefer pure expressions to side-eects because they are easier to reason about. See the sec on
on Subs tu on for more informa on. We wont use blocks in anger un l the next chapter when we start declaring
intermediate values and re-use them in later expressions.
12
1.2
Images
Numbers and text are boring. Lets switch to something more interes ngimages! Grab the Doodle project from
h ps://github.com/underscoreio/doodle. This toy drawing library will provide the basis for most of the exercises in this
course. Start a Scala console to try Doodle out:
bash$ git clone https://github.com/underscoreio/doodle.git
# Cloning ...
bash$ cd doodle
bash$ ./sbt.sh console
[info] Loading project definition from /.../doodle/project
[info] Set current project to doodle (in build file:/.../doodle/)
[info] Compiling 1 Scala source to /.../doodle/jvm/target/scala-2.11/classes...
[info] Starting scala interpreter...
[info]
import doodle.core._
import doodle.syntax._
import doodle.jvm._
Welcome to Scala version 2.11.5 (Java HotSpot(TM) 64-Bit Server VM, Java 1.7.0_45).
Type in expressions to have them evaluated.
Type :help for more information.
scala>
1.2.1
Primi ve Images
The Doodle console gives us access to some useful drawing tools as well as the regular Scala standard library. Try crea ng
a simple shape:
scala> Circle(10)
res0: doodle.core.Circle = Circle(10.0)
No ce the type and value of the expression we just entered. The type is doodle.core.Circle and the value is Circle(10.0)a circle with a 10 pixel radius.
We can draw this circle (and other images) using Doodles draw() func on, which has been brought into scope automa cally. Try drawing the circle now:
scala> draw(res0)
1.2. IMAGES
13
14
1.2.2
Layout
We can write expressions to combine images producing more complex images. Try the following codeyou should see a
circle and a rectangle displayed beside one another:
scala> draw(Circle(10) beside Rectangle(10, 20))
1.2. IMAGES
15
Operator
Type
Descrip on
Example
Image
Image
Image
Image on Image
Image
Image
Create a line drawing of an archery target with three concentric scoring bands:
1.2.3
Colour
In addi on to layout, Doodle has some simple operators to add a splash of colour to our images. Try these out to see how
they work:
Operator
Type
Descrip on
Example
Image
Circle(10) fillColor
Image
Image
Color.red
Circle(10) lineColor
Color.blue
Circle(10) lineWIdth 3
16
Doodle has various ways of crea ng colours. The simplest are the predened colours in shared/src/main/scala/doodle/core/Comm
Here are a few of the most important:
Color
Type
Example
Color.red
Color
Color.blue
Color
Color.green
Color
Color.black
Color
Color.white
Color
Color.gray
Color
Color.brown
Color
Colour your target red and white, the stand in brown (if applicable), and some ground in green:
17
As programmers we must develop a mental model of how our code operates. In the absence of side-eects, the
substu on model always works. If we know the types and values of each component of an expression, we know the
type and value of the expression as a whole.
Func onal programmers aim to avoid side-eects for this reason: it makes our programs easy to reason about without
having to look beyond the current block of code.
1.3.2
Types in Scala
Weve seen several types so far, including primi ve Scala types such as Int, Boolean, and String, the Date type from
the Java standard library, List from the Scala standard library, and the Circle, Rectangle, Image, and Color types from
Doodle. Lets take a moment to see how all of these t together:
Any
AnyVal
AnyRef
= java.lang.Object
Int
= java int
Double
Boolean
= java double
= java boolean
Unit
java.lang.String
~ java void
Array[T]
= java array
All Java
classes
All Scala
classes
etc
AnyVal is a supertype of the JVMs xed set of value types, all of which which we know from Java: Int is int,
Boolean is boolean, and so on. AnyVal is also the supertype of Unit, which we will discuss in a moment.
AnyRef is the supertype of all JVM reference types. It is an alias for Javas Object type. AnyRef is the supertype
of all Java and Scala classes.
The Unit type is Scalas equivalent of void in Java or Cwe use it to write code that evaluates to no interes ng value:
scala> val uninteresting = println("Hello world!")
Hello world!
uninteresting: Unit = ()
While void is simply a syntax, Unit is an actual type with a single value, (). Having an concrete type for Unit and value
allows us to reason about side-eec ng code with the same principles as func onal code. This is essen al for a language
like Scala that bridges the worlds of the impera ve and the func onal.
We have so far seen two impera ve-style methods that return Unit: the println method from the Scala standard library
and Doodles draw method. Each of these methods does something useful but neither returns a useful result:
scala> val alsoUninteresting = draw(Circle(10))
alsoUninteresting: Unit = ()
18
Designing programs in a func onal way involves limi ng the side-eects spread throughout our code. Doodle is a classic
example of func onal designwe assemble a representa on of the scene we want in a purely func onal manner, and then
interpret the scene to produce output. The draw() methodour interpretercan use impera ve libraries and mutable
state without them intruding into the rest of our applica on.
Chapter 2
Declara ons
Not all programs are single expressions. Some mes it is useful to bind expressions to a name and re-use them later. These
constructs are called declara ons. Declara ons themselves dont have a type or a value. However, they do bind names
that can be used as expressions. There are several types of declara on in Scala as we shall see below.
2.1
The simplest type of declara on binds a name to the result an expression. These are called value declara ons. They are
similar to variable declara ons, except we cannot assign new values to them a er declara on:
scala> val blackSquare = Rectangle(30, 30) fillColor Color.black
blackSquare: doodle.Image = // ...
scala> val redSquare = Rectangle(30, 30) fillColor Color.red
redSquare: doodle.Image = // ...
Create an 8x8 square chess board without loops or comprehensions, using the deni ons of redSquare and blackSquare
above. Use intermediate value declara ons to make your code as compact as possible:
See the solu on
2.2
Some mes we want to repeat a process, but each me we do it we change some part of what were doing. For example,
imagine crea ng chess boards where each chess board has a dierent combina on of colors. It would be extremely
tedious to declara on each chess board as we have above for each choice of colors. What wed like is to be able to dene
some process for making chess boards that allows the user to specify the color choice for the par cular chess board were
making. This is what methods allow us to do.
We have already seen method calls. In this sec on we are going to see how we can declare our own methods. Like value
declara ons, method declara ons dene a name. Instead of giving a name to a value, a method declara on gives a name
to a process for crea ng values:
def twoByTwo(color1: Color, color2: Color): Image = {
val square1 = Rectangle(30, 30) fillColor color1
val square2 = Rectangle(30, 30) fillColor color2
(square1 beside square2) above
(square2 beside square1)
}
19
20
CHAPTER 2. DECLARATIONS
21
This declares a method called twoByTwo. The method has two parameters, called color1 and color2, which we have
declared to have type Color. We have also declared the type of the value returnedImage. The body of the method,
which is enclosed with op onal braces (the { and } pair) denes how we create the Image. This mirrors the process for
crea ng a two by two chess board that we saw above, but in this case we are using the colors we are passed as parameters.
Exercise: Chess Board
Declare a method called fourByFour that constructs a four-by-four chess board using twoByTwo declared above. The
method should have two parameters, both Colors, that it passes on to twoByTwo.
You should be able to call fourByFour like so
fourByFour(Color.cornflowerBlue, Color.seaGreen) beside
fourByFour(Color.chocolate, Color.darkSalmon)
Syntax summary
Weve seen quite a lot of Scala syntax so far. If you cant remember everything weve covered, dont panic! Theres
a syntax quick reference in the appendices at the end of the book.
2.3
In this exercise we will explore the crea on of color pale es. An a rac ve picture must make good choices for color.
Color theory has developed to explain combina ons of color that go together. We will use color theory, and some neat
tricks from mathema cs and computer science, to create programs that can automa cally create a rac ve color pale es.
22
CHAPTER 2. DECLARATIONS
2.3.1
Color Theory
Figure 2.3: A color wheel. A full turn around the wheel represents a 360 degree change in hue.
A simple way to generate colors that look good together is to use complementary colors. Given a color, its complement
is the one opposite it on the color wheel. In other words, it has hue rotated by 180 degrees. Complementary pairs have
23
Figure 2.4: Aubergines by Estaban Cavrico CC BY-NC-ND 2.0. The green and purple of the aubergins are near complements.
Exercise: Complementary Colors
Create a method complement that takes a Color as input and returns its complement. You can use the method spin on
a Color to rotate its hue by a given Angle.
See the solu on
Exercise: Complementary Chess Boards
Using complement write a method complementaryChessBoard that creates a four-by-four chess board using a complementary color scheme. This method should take a Color input. Heres the method signature:
def complementaryChessBoard(color: Color): Image = ???
2.3.2
Analogous Colors
Complementary colors can be quite harsh on the eyes. We can play around with satura on and lightness to decrease the
contrast but ul mately this color scheme is quite limited. Lets explore another color scheme, analogous colors, that gives
us more exibility.
In analogous color is simply one that is close on the color wheel to a given color. We can generate an analogous color by
spinning hue, say, een degrees.
24
CHAPTER 2. DECLARATIONS
Create a method analogous that takes a Color as input and returns an analogous color.
See the solu on
Exercise: Analogous Chess Boards
Now create a method analogousChessBoard that creates a four-by-four chess board with an analogous color scheme.
You should get a result like the below.
See the solu on
2.3.3
We have seen how we can build very simple color pale es from complementary and analogous colors. Now lets combine
these ideas to build more complex pale es. A tetrad color scheme consists of two analogous colors and their complements.
Dene a method tetradChessBoard that creates a chess board colored with a tetradic color scheme as illustrated. Use
the following skeleton
def tetradChessBoard(color: Color) = ???
Hint: You will have to call twoByTwo, not fourByFour, within the body of tetradChessBoard.
See the solu on
2.4
25
26
CHAPTER 2. DECLARATIONS
The chessboard example demonstrates this nicelyit re-uses the fourByFour value four mes and the twoByTwo value
sixteen mes, resul ng in a compact memory-ecient representa on.
Method declara ons to a dierent job. They allow us to abstract over parameters, crea ng blocks of code that work with a
variety of inputs. Func onal programming places emphasis on wri ng methods that return useful values, eec vely turning
methods into high-level constructors.
For example, we can view a method like tetradChessBoard() as a constructor for a chess board. Even though the
method creates many objects internally, the subs tu on model allows us to ignore the implementa on details and treat
the method as a black box.
Chapter 3
3.1
Recursive Algorithms
Recursion is a natural part of func onal programming. The classic func onal data structurethe single linked listis
recursive in nature. We can similarly create interes ng drawings using recursion.
Lets start with a simple examplea set of concentric circles. We can create this image by recursing over the natural
numbers. Each number corresponds to the next layer of the image:
n=1
n=2
n=3
27
28
29
Create an image containing 20 concentric circles using the approach described above:
For extra credit, give each circle its own hue or opacity by gradually changing the colour at each level of recursion:
Sierpinski triangles are a more interes ng example of a recursive drawing algorithm. The pa ern is illustrated below:
Here is an English descrip on of the recursive pa ern:
The base case for n = 1 is an equilateral triangle. We can draw this in Doodle as follows:
Triangle(10, 10)
Every other case involves three copies of the n - 1 case arranged in a triangle.
30
n=1
n=2
n=3
n=4
Figure 3.4: Sierpinski triangles (n = 1 to 4)
Use this descrip on to write Scala code to draw a Sierpinski triangle. Start by dealing with the n = 1 case, then solve the
n = 2 case, then generalise your code for any value of n. Finish by drawing the n = 10 Sierpinkski triangle below:
You may no ce that the nal result is extremely large! For extra credit, rewrite your code so you can specify the size of
the triangle up front:
def sierpinski(n: Int, size: Double): Image = ???
Finally, for double extra credit, answer the following ques ons:
1. How many pink triangles are there in your drawing?
2. How many Triangle objects is your code crea ng?
3. Is this the answer to ques on 2 necessarily the same as the answer to ques on 1?
4. If not, what is the minimum number of Triangles needed to draw the n = 10 Sierpinski?
See the solu on
3.2
The dening feature of a func onal programming programming language is the ability to dene func ons that are rst class
values. Scala has special syntax for func ons and func on types. Heres a func on that calculates
scala> (a: Double, b: Double) => math.sqrt(a*a + b*b)
res0: (Double, Double) => Double = <function2>
scala> res0(3, 4)
res1: Double = 5.0
Because Scala is an object oriented language, all rst class values are objects. This means func ons are objects, not
methods! In fact, func ons themselves have useful methods for composi on:
31
32
It may seem surprising and restric ve that Scala methods are not values. We can prove this by a emp ng to refer to a
method without invoking it:
scala> Color.rgb
<console>:20: error: missing arguments for method rgb in object Color;
follow this method with `_' if you want to treat it as a partially applied function
Color.rgb
^
Fortunately, as the error message above suggests, we can convert any method to a func on using the _ operator and call
it with the same parameters:
scala> Color.rgb _
res4: (Int, Int, Int) => doodle.core.Color = <function3>
scala> res4(255, 0, 0)
res5: doodle.core.Color = ...
3.3
Why are func ons useful? We can already use methods to package up and name reusable fragments of code. What other
advantages do we get from trea ng code as values?
we can pass func ons as parameters to other func ons and methods;
we can create methods that return func ons as their results.
Lets consider the pa ern from the concentric circles exercise as an example:
def manyShapes(n: Int): Image =
if(n == 1) {
singleShape
} else {
singleShape on manyShapes(n - 1)
}
def singleShape: Image = ???
This pa ern allows us to create many dierent images by changing the deni on of singleShape. However, each me
we provide a new deni on of singleShape, we also need a new deni on of manyShapes to go with it.
We can make manyShapes completely general by supplying singleShape as a parameter:
33
Now we can re-use the same deni on of manyShapes to produce plain circles, circles of dierent hue, circles with
dierent opacity, and so on. All we have to do is pass in a suitable deni on of singleShape:
// Passing a function literal directly:
val blackCircles: Image =
manyShapes(10, (n: Int) => Circle(50 + 5*n))
// Converting a function to a method:
def redCircle(n: Int): Image =
Circle(50 + 5*n) lineColor Color.red
val redCircles: Image =
manyShapes(10, redCircle _)
Warning
TODO: Recap the syntax for func on types and func on values.
Exercise: The Colour and the Shape
Star ng with the code below, write color and shape func ons to produce the following image:
34
The manyShapes method is equivalent to the concentricCircles method from previous exercises. The main dierence
is that we pass in the deni on of singleShape as a parameter.
Lets think about the problem a li le. We need to do two things:
1. write an appropriate deni on of singleShape for each of the three shapes in the target image;
2. call manyShapes three mes, passing in the appropriate deni on of singleShape each me and pu ng the
results beside one another.
Lets look at the deni on of the singleShape parameter in more detail. The type of the parameter is Int => Image,
which means a func on that accepts an Int parameter and returns an Image. We can declare a method of this type as
follows:
def outlinedCircle(n: Int) =
Circle(n * 10)
We can pass a reference to this method to manyShapes to create an image of concentric black outlined circles:
draw(manyShapes(10, outlinedCircle))
35
Figure 3.8: Many outlined circles beside many circles and squares
For extra credit, when youve wri en your code to create the sample shapes above, refactor it so you have two sets of base
func onsone to produce colours and one to produce shapes. Combine these func ons using a combinator as follows,
and use the result of the combinator as an argument to manyShapes
def colored(shape: Int => Image, color: Int => Color): Int => Image =
(n: Int) => ???
3.4
Warning
36
Chapter 4
Collec ons
An introduc on to func onal programming wouldnt be complete without discussing transforma ons on collec ons. In
this chapter we will look at the Lists, which are without a doubt the best known data type in func onal programming.
4.1
Crea ng Sequences
The standard library in Scala contains many types of sequence: mutable and immutable, lazy and eager, parallel and
sequen al. In this course we will use two types of sequence: Lists and Ranges. Both are simple, immutable, eager data
types. Lets see them in ac on.
We can create a list in Scala by calling the List factory method as follows:
scala> List(1, 2, 3, 4, 5)
res0: List[Int] = List(1, 2, 3, 4, 5)
The result of the expression is of type List[Int], which we read as list of integers. If we call the factory method with
String arguments, the type of the result changes accordingly:
scala> List("a", "b", "c", "d", "e")
res1: List[String] = List(a, b, c, d, e)
Lists are useful for storing short sequences of values. If we want to create long sequences of numbers, however, we are
be er o using Ranges. We can create these using the until method of Int or Double:
scala> 0 until 10
res0: scala.collection.immutable.Range.Inclusive =
Range(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
scala> 0.0 until 5.0
res1: scala.collection.immutable.Range.Inclusive =
Range(0.0, 1.0, 2.0, 3.0, 4.0)
Ranges have a by method that allows us to change the step between consecu ve elements of the range:
scala> 0 until 10 by 2
res1: scala.collection.immutable.Range.Inclusive =
Range(0, 2, 4, 6, 8)
scala> 0.0 until 1.0 by 0.3
res2: scala.collection.immutable.Range.Inclusive =
Range(0.0, 0.3, 0.6, 0.9)
37
38
CHAPTER 4. COLLECTIONS
Many methods in Doodle are designed to work with Lists and Ranges, but you can use the toList of any Range to
convert it to a List if you run into problems:
scala> (0 until 10).toList
res0: List[Int] =
List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
4.2
Transforming Sequences
In impera ve programs we use loops to iterate over lists and transform them to produce new values. For example, here
is a Java 7 method to double the numbers in an ArrayList:
List<Integer> doubleAll(List<Integer> numbers) {
List<Integer> ans = new ArrayList<Integer>();
for(int i : numbers) {
ans.push(i * 2);
}
return ans;
}
List<Integer> result = doubleAll(new ArrayList<Integer>(1, 2, 3, 4, 5));
There are a lot of lines in this example, many of which arent to do with the desired opera on of doubling numbers. We
have to alloate a temporary list and push numbers onto it before returning it, all of which ought to be be handled by library
code.
We cant easily abstract away the temporary list alloca on in Java 7 because we have no direct way of abstrac ng the
doubling opera on. In Scala and Java 8 we can represent doubling succinctly using a func on literal, aka a closure:
scala> (x: Int) => x * 2
res2: Int => Int = <function1>
scala> res2(10)
res3: Int = 20
Scalas List class has a method called map that allows us to exploit func ons to remove all of the boilerplate from our
Java 7 example. map accepts a func on as a parameter and returns a new List created by applying the func on to every
item:
scala> List(1, 2, 3, 4, 5).map(i => i * 2)
res4: List[Int] = List(2, 4, 6, 8, 10)
We can use the map method to convert Lists of values to Lists of Images:
scala> val radii = List(10, 20, 30, 40, 50)
radii: List[Int] = List(10, 20, 30, 40, 50)
scala> val circles = radii.map(i => Circle(i * 10))
circles: List[doodle.core.Circle] = // ...
Doodle contains a handful of convenient methods to convert values of type List[Image] to single Images. One of these
is allBeside, which lays a list of images out beside one another:
39
scala> draw(allBeside(circles))
Type
Descrip on
Example
allBeside(listOfImages)
Image
allBeside(Circle(10),
allAbove(listOfImages)
Image
allBelow(listOfImages)
Image
allOn(listOfImages)
Image
allUnder(listOfImages)
Image
Circle(20))
allAbove(Circle(10),
Circle(20))
allBelow(Circle(10),
Circle(20))
allOn(Circle(10), Circle(20))
allUnder(Circle(10),
Circle(20))
We can recreate our concentric circles example trivially using allOn or allUnder. Much simpler than wri ng a recursive
method!
scala> val radii = List(10, 20, 30, 40, 50)
radii: List[Int] = List(10, 20, 30, 40, 50)
scala> val circles = radii.map(i => Circle(i))
circles: List[Circle] = // ...
scala> draw(allOn(circles))
Create an applica on to show the range of colours you can get in HSL space. Create a two-dimensional grid of rectangles,
with hue varying from 0 to 360 degrees on the x-axis and lightness varying from 0.0 to 1.0 on the y-axis.
40
CHAPTER 4. COLLECTIONS
41
For extra credit, allow the user to specify parameters for the step size along each axis and the basic shape used in each
cell:
42
CHAPTER 4. COLLECTIONS
Start with
Method
Parameter
Result
Descrip on
List[A]
map
A => B
List[B]
List[A]
filter
A => Boolean
List[A]
List[A]
flatMap
A => List[B]
List[B]
List[A]
find
A => Boolean
Option[B]
4.3
Warning
Chapter 5
Summary
In this text we have covered a handful of the essen al func onal programming tools available in Scala.
5.1
We started by wri ng expressions to create and compose images. Each program we wrote went through two dis nct
phases:
1. Build an Image
2. Call the draw() method to display the image
This process demonstrates two important func onal programming pa erns: building intermediate representa ons of the
result we want, and interpre ng the representa ons to produce output.
5.2
Abstrac on
Building an intermediate representa on allows us to only model the aspects of the result that we consider important and
abstract irrelevant details.
For example, Doodle directly represents the primi ve shapes and geometric rela onships in our drawings, without worrying about implementa on details such as screen coordinates. This keeps our code clear and maintainable, and limits
the number of magic numbers we need to write. For example, it is a lot easier to determine that this Doodle program
produces a house:
scala> def myImage: Image =
|
g.setStroke(new BasicStroke(1.0f))
path.moveTo(25, 0)
path.lineTo(50, 50)
path.lineTo(0, 50)
path.lineTo(25, 0)
path.closePath()
43
44
CHAPTER 5. SUMMARY
g.draw(path)
| }
Its important to realise that all of the impera ve Java2D code is s ll present in Doodle. The dierence is we have hidden
it away into the draw() method. draw() acts as interpreter for our Images, lling in all of the details about coordinates,
paths, and graphics contexts that we dont want to think about in our code.
Separa ng the immediate value and the interpreter also allows us to change how interpreta on is performed. Doodle
already comes with two interpreters, one of which draws in the Java2D framework while the other draws in the HTML
canvas. You can image yet more interpreters to, for example, achieve ar s c eects such as drawing images in a handdrawn style.
5.3
Composi on
In addi on to making our programs clearer, the func onal approach employed by Doodle allows us to compose images
from other images. For example, we can re-use our house to draw a street:
scala> val house = Triangle(50, 50) above Rectangle(50, 50)
house: Image = // ...
scala> val street = house beside house beside house
street: Image = // ...
The Image and Color values we create are immutable so we can easily re-use a single house three mes within the same
image.
This approach allows us to break down a complex image into simpler parts that we then combine together to create the
desired result.
Reusing immutable data, a technique called structure sharing, is the basis of many fast, memory ecient immutable data
structures. The quin ssen al example in Doodle is the Sierpinski triangle where we re-used a single Triangle object to
represent an image containing nearly 20,000 dis nct coloured triangles.
5.4
Expression-Oriented Programming
Scala provides convenient syntax to simplify crea ng data structures in a func onal manner. Constructs such as condionals, loops, and blocks are expressions, allowing us to write short method bodies without declaring lots of intermediate
variables. We quickly adopt a pa ern of wri ng short methods whose main purpose is to return a value, so omi ng the
return keyword is also a useful shorthand.
5.5
Scalas type system helps us by checking our code. Every expression has a type that is checked at compile me to see if
it matches up with its surroundings. We can even dene our own types with the explicit purpose of stopping ourselves
from making mistakes.
A simple example of this is Doodles Angle type, which prevents us confusing numbers and angles, and degrees and
radians:
45
scala> 90
res0: Int = 90
scala> 90.degrees
res1: doodle.core.Angle = Angle(1.5707963267948966)
scala> 90.radians
res2: doodle.core.Angle = Angle(2.0354056994857643)
scala> 90.degrees + 90.radians
res3: doodle.core.Angle = Angle(3.606202026280661)
scala> 90 + 90.degrees
<console>:20: error: overloaded method value + with alternatives:
(x: Double)Double <and>
(x: Float)Float <and>
(x: Long)Long <and>
(x: Int)Int <and>
(x: Char)Int <and>
(x: Short)Int <and>
(x: Byte)Int <and>
(x: String)String
cannot be applied to (doodle.core.Angle)
90 + 90.degrees
^
5.6
We spent a lot of me wri ng methods to produce values. Methods let us abstract over parameters. For example, the
method below abstracts over colours to produce dierent coloured dots:
scala> def dot(color: Color): Image =
|
Coming from object oriented languages, methods are nothing special. More interes ng is Scalas ability to turn methods
into func ons that can be passed around as values:
scala> def spectrum(shape: Color => Image): Image =
|
We wrote a number of programs that used func ons as values, but the quin ssen al example was the map method of
List. In the Collec ons chapter we saw how map lets us transform sequences without alloca ng and pushing values onto
intermediate buers:
scala> List(1, 2, 3).map(x => x * 2)
res0: List[Int] = List(2, 4, 6)
Func ons, and their rst class status as values, are hugely important for wri ng simple, boilerplate-free code.
46
5.7
CHAPTER 5. SUMMARY
Final Words
The inten on of this book has been to introduce you to the func onal parts of Scala. These are what dieren ate Scala
from older commercial languages such as Java and C. However, this is only part of Scalas story. Many modern languages
support func onal programming, including Ruby, Python, Javascript, and Clojure. How does Scala relate to these languages, and why would you want to choose it over the other available op ons?
Perhaps the most signicant draw to Scala is its type system. This dis nguishes Scala from popular such as Ruby, Python,
Javascript, and Clojure, which are weakly typed. Having strong types in a language is undeniably a trade-owri ng code
is slower because we have to sa sfy the compiler at every stage. However, once our code compiles we gain condent
about its quality.
Another major draw is Scalas blending of object-oriented and func onal programming paradigms. We saw a li le of this
in the rst chapterevery value is an object with methods, elds, and a class (its type). However, we havent created any
of our own data types in this book. Crea ng types is synonymous with declaring classes, and Scala supports a full gamut
of features such as classes, traits, interitance, and generics.
Finally, a major benet of Scala is its compa bility with Java. In many ways Scala can be seen as a superset of Java, and
interopera on between the two languages is quite straigh orward. This opens up a world of Java libraries to our Scala
applica ons, and allows exibility when transla ng Java applica ons to Scala.
5.8
Next Steps
We hope you enjoyed Crea ve Scala and drawing diagrams with Doodle. If you would like to learn more about Scala, we
recommend that you pick one of the many great books available on the language.
Our own book, Essen al Scala, is available from our web site and con nues Crea ve Scalas approach of teaching Scala
by discussing and demonstra ng core design pa erns and the benets they oer.
Appendix A
// Literals:
123
// Int
123.0
// Double
"Hello!" // String
true
// Boolean
// Math:
10 + 2
// Int + Int
= Int
// Int / Int
= Double
// Boolean logic:
true && false // logical AND
true || false // logical OR
!true
// logical NOT
// String concatenation:
"abc" + "def" // String
"abc" + 123
1 + 2
1 + 2 + 3 // equivalent to 1.+(2).+(3)
// Conditionals:
if(booleanExpression) expressionA else expressionB
// Blocks:
{
sideEffectExpression1
sideEffectExpression2
resultExpression
}
A.2
47
48
A.3
A.4
49
imageB // superimposed
imageB // superimposed
val i: Image = image lineWidth integer // new line width (doesn't change fill)
val i: Image = image fillColor color lineColor otherColor // new fill and line
// Styling images using method call syntax:
val i: Image = imageA.fillColor(color)
val i: Image = imageA.fillColor(color).lineColor(otherColor)
// etc...
// Basic colors:
val c: Color = Color.red
// predefined colors
// RGB color
// RGBA color
// HSL color
10.degrees
// change hue
50
Appendix B
The simplest solu on is to create three concentric circles using the on operator:
draw(Circle(10) on Circle(20) on Circle(30))
For the extra credit we can create a stand using two rectangles:
draw(
Circle(10) on
Circle(20) on
Circle(30) above
Rectangle(6, 20) above
Rectangle(20, 6)
)
B.1.2
The trick here is using parentheses to control the order of composi on. The fillColor(), lineColor(), and
lineWidth() methods apply to a single imagewe need to make sure that image comprises the correct set of shapes:
draw(
( Circle(10) fillColor Color.red ) on
( Circle(20) fillColor Color.white ) on
( Circle(30) fillColor Color.red lineWidth 2 ) above
( Rectangle(6, 20) above Rectangle(20, 6) fillColor Color.brown ) above
( Rectangle(80, 25) lineWidth 0 fillColor Color.green )
)
B.2
B.2.1
Declara ons
Solu on to: Value Declara ons
An 8x8 chess board can be decomposed into four 4x4 boards, each consis ng four 2x2 boards, each consis ng four
squares:
51
52
val twoByTwo =
(redSquare
This is signicantly clearer and more compact than crea ng the whole board in one expression:
val b = Rectangle(30, 30) fillColor Color.black
val r = Rectangle(30, 30) fillColor Color.red
val chessBoard =
(r beside b beside r beside b beside r beside b beside r beside b) above
(r beside b beside r beside b beside r beside b beside r beside b) above
(r beside b beside r beside b beside r beside b beside r beside b) above
(r beside b beside r beside b beside r beside b beside r beside b) above
(r beside b beside r beside b beside r beside b beside r beside b) above
(r beside b beside r beside b beside r beside b beside r beside b) above
(r beside b beside r beside b beside r beside b beside r beside b) above
(r beside b beside r beside b beside r beside b beside r beside b)
B.2.2
The structure of fourByFour is iden cal to twoByTwo except that we use twoByTwo to construct the squares we build
the board.
def fourByFour(color1: Color, color2: Color): Image = {
val square = twoByTwo(color1, color2)
(square beside square) above
(square beside square)
}
B.2.3
B.2. DECLARATIONS
B.2.4
53
We can build the method using the methods we have already created.
def complementaryChessBoard(color: Color) =
fourByFour(color, complement(color))
B.2.5
B.2.6
This follows the same pa ern as complementaryChessBoard. No ce how we build big things (a colored chess board)
out of smaller component parts. This idea of composing small pieces of code into larger pieces is one of the key ideas in
func onal programming.
def analogousChessBoard(color: Color) =
fourByFour(color, analogous(color))
B.2.7
It would be nice to have a method for crea ng an en re tetradic color scheme from a single color, but we dont currently
have a way of wrapping up a collec on of data so that we could return all four values from the methods. Well see ways
of doing this later, when we introduce classes and collec ons.
def tetradChessBoard(color: Color) = {
val color1 = color
val color2 = analogous(color)
val color3 = complement(color)
val color4 = complement(color2)
val square1 = twoByTwo(color1, color3)
val square2 = twoByTwo(color2, color4)
(square1 beside square2) above
(square2 beside square1)
}
54
B.3
B.3.1
The basic structure of our solu on involves two methods: one for drawing a single circle and one for drawing n circles:
def singleCircle(n: Int): Image =
???
def concentricCircles(n: Int): Image =
if(n == 1) {
singleCircle(n)
} else {
singleCircle(n) on concentricCircles(n - 1)
}
concentricCircles(20)
There is a clean division of labour here: concentricCircles handles the recursion through values of n and the composi on of the shapes at each level, while singleCircle decides which actual shapes we draw at each level.
Here is the implementa on of singleCircle we need to draw monochrome circles. We calculate an appropriate radius
from the value of n provided. The n = 1 circle has radius 55 and each successive circle is 5 pixels larger:
def singleCircle(n: Int): Image =
Circle(50 + 5 * n)
To create mul colour circles, all we need to do is modify singleCircle. Here is an implementa on for the extra credit
example above:
def singleCircle(n: Int): Image =
Circle(50 + 5 * n) lineColor (Color.red spin (n * 10).degrees)
Here is another implementa on that fades out the further we get from n = 1:
def singleCircle(n: Int): Image =
Circle(50 + 5 * n) fadeOut (n / 20).normalized
We can make countless dierent images by tweaking singleCircle without changing the deni on of concentricCircles. In fact, concentricCircles doesnt care about circles at all! A more general naming system would be more
suitable:
def singleShape(n: Int): Image =
???
def manyShapes(n: Int): Image =
if(n == 1) singleShape(n) else (singleShape(n) on manyShapes(n - 1))
B.3.2
55
As we hinted above, each successive triangle in the Sierpinski pa ern is twice the size of its predecessor. Even if we start
with an n = 1 triangle of side 1, we end up with an n = 10 triangle of side 1024!
The extra credit solu on involves specifying the desired size up front and dividing it by two each me we recurse:
def triangle(size: Double): Image =
Triangle(size, size) lineColor Color.magenta
def sierpinski(n: Int, size: Double): Image =
if(n == 1) {
triangle(size)
} else {
val smaller = sierpinski(n - 1, size / 2)
smaller above (smaller beside smaller)
}
sierpinski(10, 512)
B.3.3
56
}
def rainbowCircle(n: Int) = {
val color = Color.blue desaturate 0.5.normalized spin (n * 30).degrees
val shape = Circle(50 + n*12)
shape lineWidth 10 lineColor color
}
def fadingTriangle(n: Int) = {
val color = Color.blue fadeOut (1 - n / 20.0).normalized
val shape = Triangle(100 + n*24, 100 + n*24)
shape lineWidth 10 lineColor color
}
def rainbowSquare(n: Int) = {
val color = Color.blue desaturate 0.5.normalized spin (n * 30).degrees
val shape = Rectangle(100 + n*24, 100 + n*24)
shape lineWidth 10 lineColor color
}
val answer =
manyShapes(10, rainbowCircle) beside
manyShapes(10, fadingTriangle) beside
manyShapes(10, rainbowSquare)
However, there is some redundancy here: rainbowCircle and rainbowTriangle, in par cular, use the same deni on
of color. There are also repeated calls to lineWidth(10) and lineColor(color) that can be eliminated. The extra
credit solu on factors these out into their own func ons and combines them with the colored combinator:
def manyShapes(n: Int, singleShape: Int => Image): Image =
if(n == 1) {
singleShape(n)
} else {
singleShape(n) on manyShapes(n - 1, singleShape)
}
def colored(shape: Int => Image, color: Int => Color): Int => Image =
(n: Int) =>
shape(n) lineWidth 10 lineColor color(n)
def fading(n: Int): Color =
Color.blue fadeOut (1 - n / 20.0).normalized
def spinning(n: Int): Color =
Color.blue desaturate 0.5.normalized spin (n * 30).degrees
def size(n: Int): Double =
50 + 12 * n
def circle(n: Int): Image =
Circle(size(n))
def square(n: Int): Image =
Rectangle(2*size(n), 2*size(n))
def triangle(n: Int): Image =
Triangle(2*size(n), 2*size(n))
val answer =
manyShapes(10, colored(circle, spinning)) beside
B.4. COLLECTIONS
57
B.4
Collec ons
B.4.1
First lets dene a method to create a single square. Well call the method cell to keep the naming shape-independent
and specify size, hue, and lightness as parameters:
def cell(size: Int, hue: Int, lightness: Double): Image =
Rectangle(size, size) lineWidth 0 fillColor Color.hsl(hue, 1.0, lightness)
Next lets create a single column of varying lightness. We start with a list of lightness values, map over the list to produce
the squares, and build the column using allAbove or allBelow:
def column(cellSize: Int, hue: Int): Image = {
val cells =
(0.0 until 1.0 by 0.01).toList map { lightness =>
cell(cellSize, hue, lightness)
}
allAbove(cells)
}
Finally lets assemble the columns into a pale e. We start with a list of hues, map over it to create columns, and build the
pale e using allBeside:
def palette(cellSize: Int): Image = {
val columns =
(0 until 360 by 2).toList map { hue =>
column(cellSize, hue)
}
allBeside(columns)
}
For the extra credit solu on we add hStep, lStep, and cell parameters. hStep and lStep are of type Int and Double
respec vely, and cell is of type (Int, Double) => Image. In the example below we use a type alias to make the type
of the cell parameter more explicit. Type aliases are simply a way of naming typesthe compiler treats an aliased type
exactly the same as an unaliased one:
// Type alias for cell constructor functions:
type CellFunc = (Int, Double) => Image
// Different types of cell:
def squareCell(size: Int): CellFunc =
(hue: Int, lightness: Double) =>
Rectangle(size, size) lineWidth 0 fillColor Color.hsl(hue, 1.0, lightness)
58