MS Cloud Design Patterns Infographic 2015
MS Cloud Design Patterns Infographic 2015
Study
Noel Welsh and Dave Gurnell
Pre-release Version, August 2015
underscore
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
1 Introduc on 5
2 Founda on 7
2.1 Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
2.4 Implementa on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
3 Anima on 15
3.1 Background . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
3.3 Interface . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3.4 Implementa on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
A.1 Founda on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
A.2 Anima on . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
3
4 CONTENTS
Chapter 1
Introduc on
This supplement to Essen al Scala brings together the book’s main concepts in a sizable case study. The object
is to develop a two-dimensional vector graphics and anima on library similar to Doodle. Let us first describe the
what two-dimensional graphics are, then why this makes a good case study, and finally describe the structure
of the case.
What we mean by two-dimensional graphics should be obvious (though, arguably, when we add anima ons they
become three-dimensional, in an unconven onal meaning of the term). By vector graphics we mean images
that are specified in terms of lines and curves, rather than in terms of pixels. Because of this, vector graphics
are independent of the output format. They can be rendered to high resolu on screens, such as Apple’s Re na
displays, as easiily as they can to screens with standard resolu on. They can be arbitrarily transformed without
losing informa on, unlike bitmaps that distort when they are rotated, for example. This structure is something
our library will heavily leverage.
So, why vector graphics? There are a few reasons. Firstly, graphics are fun, and hopefully something you can
enjoy studying even if you haven’t worked with computer graphics before. Having a tangible output is of great
benefit. It’s easier to get a feel for how the library works and concepts it embodies because you simply draw
the images on the screen. Finally, we’ll see they are great vehicle for the concepts taught in Essen al Scala, and
we can wrap up most of the content in the course in this one case study.
Now let’s give an overview of the structure of the case study. It is divided into three main sec ons:
1. Building the basic objects and methods for our library, which heavily uses algebraic data types and struc-
tural recursion.
2. Adding anima ons, which introduces sequencing abstrac ons such as map and fold.
3. Abstrac ng the rendering pipeline, bringing in type classes and touching briefly on some more advanced
func onal programming concepts.
Each part asks you to implement part of the library. Supplemen ng this supplement is a code repository con-
taining support code as well as working implementa ons for each exercise. There are many possible implemen-
ta ons for each exercise, each making different design tradeoffs. Our solu on is just one of these, and you
shouldn’t take it to be any more correct than other solu ons you may come up with. A primary goal of this
supplement is to understand the tradeoffs different solu ons make.
To get the most from this case study you have to do the work. While it will be valuable if you read through and
look at our solu ons, it will be much more valuable if you a empt every exercise yourself before looking at our
solu on.
Finally, we always enjoy talking to other programmers. If you have any comments or ques ons about this case
study, do drop us an email at [email protected] and [email protected].
5
6 CHAPTER 1. INTRODUCTION
Chapter 2
Founda on
In this sec on we implement the founda on for the rest of the library: the basic objects and methods for
crea ng and drawing pictures.
Before we get into this, let’s start with some background on the problem we’re trying to solve.
2.1 Background
Our goal is to make it easy to create interes ng vector graphics and anima ons. Here is a reasonable example
of an interes ng image:
7
8 CHAPTER 2. FOUNDATION
(
( 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 )
).draw
Note how we build larger images from smaller ones. For example, to build the target we place three circles on
top of one another.
Note also that layout is done at a high level. We specify where images lie in rela on to one-another, and Doodle
works out the absolute coordinates to use when rendering to the screen.
We strongly suggest playing around with Doodle before undertaking this part of the case study. The code
is online, along with many examples. You can also read Crea ve Scala, a free textbook that uses Doodle to
introduce Scala.
The typical impera ve APIs for drawing vector graphics, such as the Java 2D API or the HTML Canvas, work at
a very low level of abstrac on. This puts a lot of the burden on the programmer. An example of such as an API
is
trait Canvas {
def setStroke(stroke: Stroke): Unit
def setFill(color: Color): Unit
To draw a shape we call beginPath, then issue a series of commands (moveTo, lineTo, or, bezierCurveTo),
followed by an endPath. We can draw just the outline of the shape, in which case we next call stroke, or just
fill it (via fill), or do both. The colors used for the stroke and fill and set by calls to setStroke and setFill
In my experience, there is a lot wrong with this sort of API. We have seen that it is very inconvenient compared
to Doodle. Another problem is there is a lot of state involved. There is a single global stroke color, for example.
This makes it difficult to abstract parts of an image into methods, as they can overwrite each other’s stroke
color. Similarly it’s not defined what happens if we nest calls to beginPath, or if we call stroke or fill before
calling endPath, and so on. All of this state makes it hard to create reusable components from which we can
build up bigger pictures.
The use of a global coordinate system makes layout difficult. Imagine we have code to draw two pictures, and
we now want to put these pictures side by side. First we had be er have had the foresight to make the star ng
point of each picture a parameter to the method that draws them, or we won’t be able to shi them around.
Then we have to calculate our layout manually—work out how wide each picture is, and move them by an
appropriate amount so they are balanced across the screen. This is a lot of donkey work, and doing donkey
work is what computers are for.
We can see the impera ve API is a lot more inconvenient to use. We could of course build our own abstrac ons,
like circle above, to provide facili es like Doodle. Indeed that is exactly what we are going to do!
Now we know what is wrong with the impera ve approach, let’s think about what kind of proper es should
hold in a be er system. The focus here is not on the kinds of pictures we can draw—if we can use gradient fills,
for example—but on the process of construc ng pictures. What should that be like? Can you come up with
a short descrip on or a few terms that describe desirable proper es and what they mean in the context of a
drawing library? Take some me to think about this before reading our answer.
Now we have thought about the proper es of our system, let’s think about the most basic classes and opera ons
on those classes. By now you should have had a play with Doodle and have a good idea of it’s model. In Doodle
the “atoms” are basic geometric shapes like circles, rectangles, and triangles. The opera ons that combine the
atoms are layout methods like on and beside, and methods to manipulate stroke and fill.
10 CHAPTER 2. FOUNDATION
Design a data type to represent the “atoms” in your model. You should probably use an algebraic data type.
Hint: You might want to look at the Canvas, which is the low-level interface we’ll implement our higher-level
interface against.
Now create the method signatures for the opera ons you’ll define on your data type. You don’t need to fill in
the bodies yet—you can just leave then as ??? so the code compiles but won’t run.
You will also need a method draw that accepts a doodle.backend.Canvas and actually renders the images
using the canvas. More on that in a moment.
2.4 Implementa on
We’re now ready to implement the complete system. We have provided a framework of code to build on, which
you can find on Github. The project is laid as follows:
• the shared directory, which contains most of the code you will interact with;
• the jvm directory, which contains code specific to rendering graphics using the Java 2D library; and
• the js directory, which contains code for rendering using the web browser canvas.
Your code should go in the doodle.core package (in shared/src/main/scala/doodle/core). Within this
package you will find u li es for handling color, angles, and other code that might be useful to you.
Within the shared directory there is also the backend package that contains the Canvas interface. When you
come to actually drawing images you should assume you’ll be passed as Canvas implementa on.
• you can start the Scala console by using the console command in sbt;
• within the console, you will have a Java 2D Canvas available, which you can access by calling
Java2DCanvas.canvas;
• you will also have the contents of doodle.core automa cally imported into the console;
• you can type Scala code into the console and use it as a simple tool to test your programs.
To quickly draw something you might try code like the following within the console:
// The coordinates for this (upside down) dog in the style of Picasso comes
// from a Jeremy Kun:
// http://jeremykun.com/2013/05/11/bezier-curves-and-picasso/
canvas.setSize(500, 500)
canvas.setOrigin(-250, 200)
canvas.beginPath()
canvas.moveTo(180,280)
canvas.bezierCurveTo(183,268, 186,256, 189,244) // front leg
canvas.moveTo(191,244)
canvas.bezierCurveTo(290,244, 300,230, 339,245)
2.4. IMPLEMENTATION 11
canvas.moveTo(340,246)
canvas.bezierCurveTo(350,290, 360,300, 355,210)
canvas.moveTo(353,210)
canvas.bezierCurveTo(370,207, 380,196, 375,193)
canvas.moveTo(375,193)
canvas.bezierCurveTo(310,220, 190,220, 164,205) // back
canvas.moveTo(164,205)
canvas.bezierCurveTo(135,194, 135,265, 153,275) // ear start
canvas.moveTo(153,275)
canvas.bezierCurveTo(168,275, 170,180, 150,190) // ear end + head
canvas.moveTo(149,190)
canvas.bezierCurveTo(122,214, 142,204, 85,240) // nose bridge
canvas.moveTo(86,240)
canvas.bezierCurveTo(100,247, 125,233, 140,238) // mouth
canvas.endPath()
canvas.setStroke(Stroke(3.0, Color.black, Line.Cap.Round, Line.Join.Round))
canvas.stroke()
There is something that you might find a bit unexpected in the implementa on of your library: an image should
not been drawn un l you call the draw method. This is necessary as we need to know the en re image before
we can layout its components. Concretely, if we’re rendering one image beside another, we need to know their
heights so we can ver cally center them. If we draw images as soon as they were created we won’t know that
they should be laid out in this way. The upshot is when we call, say, image1 beside image2, we need to
represent this as a data structure somehow. When we come to do layout we also need to know the height and
width of each component image. We can easily calculate this with bounding boxes—they are easy to implement
and sufficient if we only allow horizontal and ver cal composi on.
The idea of separa ng the descrip on of the computa on (the image data structure) from the process that
carries it out (drawing) is a classic func onal programming technique, and one we will see mul ple mes.
Along the way you will probably have to implement a bounding box abstrac on.
If you’re not sure where to start follow along with the rest of this sec on. If you think you can do it yourself,
get stuck in!
When you have finished, you can compare your implementa on to mine by switching to the feature/atoms-
and-opera ons branch in your fork of the case study repository.
12 CHAPTER 2. FOUNDATION
import doodle.backend.Canvas
Our first mission is to get some visible progress, so we’ll implement draw. We’re completely ignoring layout at
this point, so you can just draw images at the origin (or anywhere else that takes your fancy).
What’s the pa ern we’ll use in the implementa on?
See the solu on
Implement draw.
See the solu on
Now we can draw stuff on the screen, let’s implement the methods to combine images: above, beside, and
on. Each method can be implemented in one line, but there is a crucial leap you need to make to implement
them. Have a think about it, and read the solu on when you’ve worked it out.
See the solu on
Now we are ready to tackle the actual layout algorithm. To work out where every Image should be placed
we need to know how much space it takes up. We can implement bounding boxes to give us this informa on.
A bounding box is simply a rectangle that encloses an image. Bounding boxes are not precise, but they are
sufficient for our choice of primi ve images and layout methods.
When combining bounding boxes we will need to know the coordinate system we use to represent their coor-
dinates. We can’t use the global canvas coordinate system—the reason we’re implemen ng this system is to
work out the loca on of images in the global system—so we need to use a coordinate system that is local to
each image. A simple choice is to say the origin is the center of the bounding box.
We can represent a bounding box as a class
final case class BoundingBox(left: Double, top: Double, right: Double, bottom: Double)
Now implement a boundingBox method (or instance variable, as you see fit) on Image that returns the bounding
box for the image.
Now we have enough informa on to do layout. Our Image is a tree. The top level boudning box tells us how big
the en re image is. We can decide the origin of this bounding box is the origin of the global canvas coordinate
system. Then we can walk down the tree (yet more structural recursion) transla ng the local coordinate system
into the global system. When we reach a leaf node (so, a primite image), we can actually draw it. We already
have the skeleton for this in draw—we just need to pass along the mapping from the local coordinate system
to the global one. We can use a method like
which we call from the standard draw with the origin coordinates ini ally set to zero.
Anima on
We are now going to add anima ons to our graphics library. To do this we will implement a library for processing
streams of events. These systems are useful in any domain that deals with streams of data, such as financeand
web analy cs.
We’ll start, as we did in the previous chapter, with some background to the problem and play around with a
be er solu on before we come to implement it ourselves.
3.1 Background
To make smooth anima ons we must account for the characteris cs of the display. Screens typically redraw
sixty mes a second, so we should create new frames at the same rate. We also need to provide our frames at
the point in me when the screen is ready to redraw, or we might observe screen tearing.
The typical impera ve solu on is to setup a callback that is called every me a new frame is needed. The
Canvas interface provides this facility, with a method setAnimationFrameCallback. We pass a func on to
setAnimationFrameCallback, and the Canvas calls this func on every me the screen is ready for a new
frame. We then call other methods on Canvas to actually create that frame.
This interface has the all problems of the impera ve approach to drawing that we abandoned in the last chapter:
it doesn’t compose, is difficult to work with, and is difficult to reason about. What would be a be er, func onal,
approach be?
When we look at a what an anima on is, we find it quite amenable to a func onal approach. Imagine we are
anima ng a ball moving about the screen. The current posi on of the ball is a func on of the previous pos on
and the current velocity.
Figure 3.1: Current posi on is equal to the previous posi on plus the current velocity.
The current velocity is itself a func on of the user input and the previous velocity.
Let’s quickly sketch out some code to make this really concrete.
15
16 CHAPTER 3. ANIMATION
Now we can calculate velocity as a func on of the velocity at the previous mestep and user input.
Loca on is a func on of the loca on at the previous me step and the velocity.
Given the current loca on we can draw a ball at that loca on. (You might not have implemented the at method
in your version of Doodle. It places an Image at the given coordinates in the local coordinate system of the
enclosing Image, or in the global coordinate system if there is no enclosing Image.)
This is a good example of func onal code: we’ve broken the problem down into small independent func ons
that we then compose to build the complete solu on. We’re s ll missing some parts though: how is user input
obtained for example?
If we ignore interac vity for now, we can actually run the code above using Lists to provide the input. We
scanLeft currentVelocity and currentLocation, and map currentBall.
You might not have seen the scan methods before. They are equivalent to fold but they collect the intermediate
results in a list. Taking summing the elements of a List using a fold like so:
List(1, 2, 3, 4).foldLeft(0){ _ + _ }
// res: Int = 10
List(1, 2, 3, 4).scanLeft(0){ _ + _ }
// res: List[Int] = List(0, 1, 3, 6, 10)
We can apply this to our Image example to get a list of intermediate image frames.
val input = List(Up, Up, Down, Down, Left, Right, Left, Right)
Our resul ng list of images is something that we could display to make an anima on.
This system is fine for rendering anima ons from prerecorded input, but how what about responding in real-
me to user input? The values of a List are all known in advance, while user input only becomes available
when keys are pressed. What we want is some kind of sequence of data where the elements are generated by
external input. Imagine, for example, something like a list of keypresses where the next element springs into
existence when the user presses a key. We’ve seen above that the basic interface of map and scanLeft allows
us to express at least some anima ons.
We can think of a list as represen ng data in space. Different list indices correspond to different loca ons in
the computer’s memory. What we want is an abstrac on that represents data in me. Indexing in this event
stream corresponds to accessing events at different mes. (We won’t actually implement indexing as it would
allow me travel, but it provides a useful conceptual model.)
The next leap is to realise that it’s the interface allowing transforma on (map, scanLeft, and so on) that is
important, not the list-like nature. We don’t want to actually store all the past inputs like we would in a list, for
example. We can imagine our transforma ons as edges in a directed acyclic graph. User input flows into the
graph and Images flow out.
To make this concrete let’s experiment with a version of the system we’ll be building. You can find this system
on the feature/event branch. We’re going to use it to make a li le ball move around the screen in response
to key presses.
We start by conver ng the Canvas callbacks for anima on frames and key presses into event streams.
import doodle.backend.Key
import doodle.core._
import doodle.event._
import doodle.jvm.Java2DCanvas
Now we’re going to convert key presses into velocity. Velocity in a vector star ng at (0, 0), and we’ll increment
or decrement it by one as appropriate on each key press. Addi onally we going to limit the x and y components
of velocity to be in the range -5 to 5. This stops the ball flying around the screen to quickly.
18 CHAPTER 3. ANIMATION
Now we update the loca on of the ball by the velocity. Loca on starts at (0,0) and this me we’re limi ng it to
be within a 600 by 600 screen.
If you play with this code you’ll find it has an annoying problem: it only updates the ball’s posi on on key presses.
What we really want is to the ball con nually moving around the screen. We can achieve this by joining the
velocity stream with the redraw stream. The resul ng stream will have a value every me there is a new value
available on either velocity and redraw. Since redraw is updated 60 mes a second (the screen refresh rate)
this will give us a ball that moves around smoothly. The following redefini on of location is sufficient.
3.3 Interface
We can start sketching an interface. Let’s call our type EventStream[A], where the type variable indicates the
type of elements that the event stream produces.
Write down an interface that captures the examples we’ve seen so far. Are there any other methods you think
we should include?
See the solu on
The EventStream interface is simpler than the Image interface, though we have new tools (type variables,
func ons) that we’re using. The implementa on, however, is more complex and there is much more varia on
in possible implementa ons. In the next sec on we’ll look at one possible implementa on, but I encourage you
to explore your own ideas.
3.4. IMPLEMENTATION 19
3.4 Implementa on
We’re now going to implement EventStream. We will start with a very simple and somewhat broken imple-
menta on, and then explore improvements of increasing complexity.
Our implementa on approach will be push-driven. This means that when a source receives an event, we will
push that event to observing nodes, and so on up the graph ll we reach a node with no observers. The
implica on is that each node must store its observers. Let’s tackle this in small steps, star ng by implemen ng
map.
Start by somehow implemen ng map without worrying about observers. Hint: use the same trick we used to
implement layout for Image.
See the solu on
Now we’re going to add in the observers. These observers are a collec on (a List will do), but of what type?
An EventStream[A] generates events of type A but it says nothing about the type of events it accepts as input,
or if it even accepts input at all. We need another type to represent this. The Observer type will do
Now we can specify that Map is an Observer[A] and an EventStream[B], though we don’t know how to
implement observe yet.
Now we can say every EventStream[A] has a collec on of observers of type Observer[A]. It doesn’t really
ma er what collec on type we use, but we’ll have to be able to update it—so it either needs to be a mutable
collec on or stored in a var. Implement this.
See the solu on
When we map over an event stream we need to add a new observer to the EventStream. Implement this.
See the solu on
20 CHAPTER 3. ANIMATION
This is the basic implementa on pa ern we will use for the rest of the methods. Before we implement the rest of
the API let’s get observe working. What are we going to do in observe? Our only concrete implementa on is
Map. In Map we want to transform the input using the func on f and then push that output to all observers. We
might (rightly) worry about the order in which we push the output, but for now we’ll ignore that ques on—any
order will do.
Implement scanLeft. Hint: you will need to introduce mutable state to store the previous output of scanLeft
that gets fed back into scanLeft when the next event arrives.
3.4.3 Infrastructure
We s ll have to implement join, but this is trickiest part of the API. Instead of tackling it now let’s work on
some of the suppor ng infrastructure so we can get some simple anima ons working.
Our first step is to implement event sources, which form the beginning of an event processing graph. In our
implementa on an event source simply pushes its input unchanged to its observers. We need to do two things:
A callback handler is a method that allows us to set a callback that is invoked when an event arrives. The Canvas
trait has two callback handlers, shown below.
/** Set a callback that will be called when the canvas is ready to display to a
* new frame. This will generally occur at 60fps. There can only be a single
* callback registered at any one time and there is no way to cancel a
* callback once it is registered.
*
* The callback is passed a monotically increasing value representing the
* current time in undefined units.
*/
def setAnimationFrameCallback(callback: Double => Unit): Unit
To represent event sources we can simply reuse Map passing in the iden ty func on. (Note that we could define
another case in our algebraic data type. I’m avoiding doing so to skirt around a type inference problem that we
will crash into later. I encourage you to a empt this alterna ve implementa on to see the error, which we will
learn how to solve when we discuss implemen ng join.)
We now need to implement our u lity to construct a source from a callback handler. Make it so.
With this in place you should be able to implement some simple anima ons.
3.5. EASING FUNCTIONS 21
3.4.4 Join
We are now ready to tackle join, the trickiest part of the system. Join takes two event streams as input, and
produces a tuple of the most recent events from both streams whenever either stream produces an event.
All our previous nodes have had a single input, whereas join has two. Therefore we can’t use the same imple-
menta on strategy as before. We also need some mutable state, so we can record the most recent event from
each stream we’re observing.
Let first implement a class that will hold the mutable state.
I’ve made the class private to the event package so we can hide this implementa on detail to the outside.
Now we can implement join. Here’s the start of the defini on I used. See if you can implement it yourself given
this start. Note that you will probably run into a compila on issue that you won’t be able to solve. Read the
solu on for more on this.
Consider anima ng a UI element, such as a toolbar that slides out. We could simply linearly interpolate the posi-
on of the toolbar between the start and end points, but this simple movement lacks visual appeal. UI designers
like to use movement that is more natural looking. For example, our toolbar’s movement might accelarate over
me, and it might bounce when it reaches it’s final des na on.
Func ons that interpoloate posi on over me are known as tweening func ons or easing func ons. The most
commonly used easing func ons come from the work of Robert Penner. His easing func ons include include
linear func ons, quadra cs and higher order polynomials, and more complica on equa ons that give oscilla ng
behaviour. (If you’ve studied physics you are probably thinking of damped spring models right now. That’s not
a bad place to start.)
In this sec on we are going to build a library of easing func ons. This is a good example of func onal design,
and it will help us make more interes ng anima ons with our anima ons library.
In our representa on, an easing func on will accept and return a number between 0 and 1. The input is the
me, from 0% to 100%. The output indicates posi on between 0% and 100% of the final posi on. The output
should always start at 0 and finish at 1, but can vary arbitrarily inbetween. The more mathema cally inclined
will recognise this as the parametric form of a path. A quadra c easing func on can then be defined as f(t) =
tˆ2, for t in [0, 1].
The obvious representa on in Scala is to use a func on Double => Double. However, not all Double => Dou-
ble func ons will be easing func ons, and we’ll want to add domain specific methods to our easing func ons.
This is makes sense to wrap our Double => Double func ons in a type like¹
¹The more perfomrance oriented of your might object to the addi onal indirec on introduced by the Easing wrapper. We can
remove it in many cases by using a value type. This goes beyond Essen al Scala, but it is a fairly simple thing to implement: simply
extend AnyVal.
22 CHAPTER 3. ANIMATION
We should also implement some basic easing func ons. Here are some equa ons to use:
Implement these func ons. You might it useful to import scala.math. Now implement an appropriate apply
method on Easing.
Just like Bilbo, we some mes want to go there and back again when anima ng objects. An ease in func on
goes from 0 to 1 ait’s input goes from 0 to 1. An ease out func on is the reverse, going from 1 to 0 as input
varies from 0 to 1. The func ons we have implemented above are all ease in func ons.
Given an ease in func on we can construct an ease out. How? We can run it backwards and take the output
away from 1. So if f is an ease in func on, g(t) = 1 - f(1-t) is the corresponding ease out.
How should we represent this in code? We can easily add a method easeOut to Easing, transforming an ease
in to an ease out. What would the result type of this method be? If it’s an Easing we could apply easeOut
again, which yields a broken Easing. Hmmm…
A be er solu on is to rename out type Easing to EaseIn and create a new type EaseOut. Add a method
easeOut to EaseIn, and a method easeIn to EaseOut. These methods should be inverses.
Implement this.
Given some basic func ons we can compose new tweening func ons from exis ng ones. For example, we
might use the quadra c above for the first half of the element’s movement, and f(t) = (t-1)ˆ2 + 1 for the second
half (so the element deccelerates towards its final posi on.)
T1. Implement a few tweening func ons. This shouldn’t take you very long.
Chapter 4
In this sec on we’ll explore uses of implicits. Most of our me will be spend implemen ng type classes, but
we’ll also look at how we can use implicits to provide a simpler user interface.
You should base your work off the feature/event branch, or your equivalent if you have implemented the
previous sec ons.
In this sec on we’ll look at a few improvements we can make to the Image API using implicits.
It’s a bit inconvenient to always explicitly pass a Canvas to the draw method on Image. Let’s add some implicit
magic to make the Canvas op onal. Make the Canvas argument of draw an implicit parameter, and makes cor-
responding changes to the canvas implementa ons to make implicit values available. Hint: look in the objects
Java2DCanvas and HtmlCanvas.
4.1.2 Syntax
The interface for anima ng an EventStream[Image] is a bit clunky. We didn’t add an animate method to
EventStream because event streams work with generic types, though it feels like this is where such a method
should live. We ended up crea ng a method animate on an object, passing it both an EventStream[Image]
and a Canvas.
We can add animate as a method to only EventStream[Image] via the magic of implicit classes. In the
package doodle.syntax (directory is shared/src/main/scala/doodle/syntax) add an implicit class called
EventStreamImageSyntax following the conven ons already in use in that package. Wire in your syntax to
the package object in package.scala following the exis ng conven ons.
When you import doodle.syntax.eventStreamImage._ you should now have an animate method avail-
able on any object of type EventStream[Image]. This method should accept a Canvas as an implicit parame-
ter, just as we have done with draw.
23
24 CHAPTER 4. TYPE CLASSES AND IMPLICITS
We’re now ready to tackle the main pa ern for which we use implicits: type classes.
When we designed our EventStream interface we drew inspira on from the exis ng API of List. It can be
useful to be able to abstract over List and EventStream. If we defined such an API, we could write event
processing algorithms in terms of this API, making them oblivious to the concrete implementa on they run on.
Then we could run our algorithms in real- me data using the EventStream API and over batch (offline) data
using Lists without making an code changes.
This is a perfect applica on for type classes. We have two types (EventStream and List) that share a common
interface but don’t share a common useful supertype. In fact their are many other types that have an API much
like EventStreams (in the standard library, Option and Future come to mind, while in Essen al Scala we have
implemented some of these methods for types like Sum). A few type classes would allow us to unify a whole
load of otherwise different types, and allow us to talk in a more abstract way about opera ons over them.
So, what should our type classes be? We have briefly discussed functors—things that have a map method. What
about join and scanLeft? Things that can be joined are called applica ve functors, or just applica ves for
short. Our scan opera on has the same signature as scanLeft on a List. There is no standard type class so
we’ll create our own called Scannable. Finally we’ll through Monads into the mix, even though EventStream
doesn’t have flatMap method, because they are so useful in other contexts.
We have an informal idea of the type classes. Now let’s get specific. For a type F[A]
Implement these type classes, pu ng your code in a package doodle.typeclasses. Create type class in-
stances (where you can) for EventStream and List. Put the EventStream instances in its companion object,
and the List instances in doodle.typeclasses.
You will run into a problem doing this. Read on for the solu on but make sure you a empt the exercise before you
do.
trait Function[F] {
def map[A,B](fa: F[A])(f: A => B): F[B]
}
and received an error like error: F does not take type parameters. To solve this problem we need to
learn about kinds and higher-kinded types.
Kinds are like types for types. The describe the number of “holes” or parameters in a type. We dis nguish
between regular types like Int and Stringthat have no holes, and type constructors like List and EventStream
that have holes that we can fill to produce types like List[Int] and EventStream[Image]. Type constructors
are analogous to func ons of a single parameter, opera ng on the type level rather than the value level.
4.2. TYPE CLASSES 25
When we write a generic type parameter like the F in trait Functor[F] we must also tell Scala its kind. As
you’ve probably guessed, no extra annota on means a regular type. To indicate a type constructor taking a
single parameter we would write F[_]. F is the name of the type constructor, and [_] indicates it has a single
parameter or hole. For example trait Functor[F[_]]
The specifying a kind on a type variable is like giving a type declara on on a regular method parameter. Just
like a parameter we don’t repeat the kind when we use the type variable. For example, if we write
trait Functor[F[_]]
this declares a type variable called F with kind [_] (so a type constructor with a single type parameter). When
we use F we don’t write the [_]. Here’s an example:
trait Functor[F[_]] {
def map[A,B](fa: F[A])(f: A => B): F[B]
}
We must enable higher kinds to use this feature of Scala, by impor ng scala.language.higherKinds.
import scala.language.higherKinds
trait Functor[F[_]] {
def map[A,B](fa: F[A])(f: A => B): F[B]
}
Using your new knowledge of higher kinded types, implement the rest of the type classes and the type class
instances.
For extra bonus points implement type class instances for normal types (i.e. for any type A). This is known as
the iden ty monad / functor / applica ve. Hint: types are not type constructors—they have the wrong kind!
However you can get the compiler to consider types as type constructors by declaring a type synonym like type
Id[A] = A.
Go hog wild, and use your new found powers to write methods producing anima ons either as a List or an
EventStream. This allows us to easily view individual frames (by producing a List) or to view the en re
anima ons (by using an EventStream).
26 CHAPTER 4. TYPE CLASSES AND IMPLICITS
Appendix A
A.1 Founda on
A compos onal library allows us to build larger pictures from smaller ones by composing them together. How
could we compose images? We have already talked about layout as one possibility. We could define a new image
as the composi on of two images beside one another. You can imagine other layout opera ons to arrange
images ver cally, or to stack them on top of one another, and so on. We could also compose images using
geometric transforma ons such as rota ons and shearing, or using styling such as fill color.
Compos onality implies there is no global state. There are many closely related terms that all boil down to
removing state: maintaining subs tu on, enabling local reasoning, referen al transparency, or purity.
Closure is another property implied by composi onality. This means there will be opera ons that take two or
more pictures and return an element of the same type. Closure allows us to apply opera ons indefinitely to
build more complex pictures from simpler ones.
Our library should allow opera ons in terms of a local coordinate system to make composi on easier.
Finally, we want an expressive library, a rather loosely defined term that we can take to mean we should write
the minimal amount of code required to achieve our goal.
We can start with a very simple model like so: “An Image is a Circle or a Rectangle”. You should be immedi-
ately be able to translate this into code.
The low-level abstrac on we are rendering to is built on paths. We could model this as, say “A PathElement is a
MoveTo, LineTo, or CurveTo” and “A Path is a sequence of PathElements”. This is actually the representa on
that Doodle uses, but we provide a higher-level interface like Image above for convenience. The Path interface
can also be directly converted into code. (At this point in Essen al Scala we haven’t seen the Seq type yet. It
represents a sequence of elements.)
27
28 APPENDIX A. SOLUTIONS TO EXERCISES
You might also want methods to add stroke and fill, but for the purposes of this case study we can leave them
out for now.
package doodle
package core
import doodle.backend.Canvas
???
With this in-place you should be able to render some simple images. From the sbt console, try
We need to represent the layout opera ons as data, which means we need to extend the Image algebraic data
type with cases for layout. Then the method bodies just constructs the correct instance that represents the
opera on.
When we add these new cases to our algebraic data type we also need to add them to draw as well (as per the
structural recursion pa ern, but the compiler will complain in any case if we forget them.) Right now we’re not
actually doing any layout so we just recurse down the data structure and draw the leaves.
package doodle
package core
import doodle.backend.Canvas
We will want methods to combine bounding boxes that mirror the methods to combine Images. So, above,
beside, and on. We might also find it useful to store the width and height.
package doodle
package core
final case class BoundingBox(left: Double, top: Double, right: Double, bottom: Double) {
val height: Double = top - bottom
More structural recursion! Note we can implement boundingBox as an instance variable as it is fixed for all
me, and therefore we don’t need to recalculate it.
package doodle
package core
import doodle.backend.Canvas
package doodle
package core
import doodle.backend.Canvas
A.2 Anima on
Let’s think for a minute about what this means. The func on f uses the input A to choose an EventStream[B]
to handle further processing, thereby possibly changing the downstream event processing network on every
input. There are event stream systems that allow this but it is tricky to implement and even trickier to reason
34 APPENDIX A. SOLUTIONS TO EXERCISES
about. For these reasons I’ve chosen to not implement flatMap but if you want an addi onal challenge you
can a empt to implement it.
By the way, scanLeft is some mes called foldP, meaning “fold over the past”. This is the name you’ll find in
the “func onal reac ve programming” literature.
Just like we did for the layout combinators (beside, on, etc.) we can represent as data the computa ons we
don’t know how to actually implement at this stage.
Note that Map extends EventStream[B]. Remember the type parameter indicates the type of events the stream
produces. We need the extra parameter A to denote the type of events that Map accepts.
I’ve chosen to use a mutable ListBuffer, but you could equally use a List stored in a var.
We can implement observe using structural recursion. At this point we’re not worried about the order in which
we update the observers, so I’ve chosen le -to-right traversal. Since each call to observe will recursively result
in another update, this choice also gives us depth-first traversal of the graph.
You might feel some unease about this solu on. A Map has both an input (of type A) and an output (of type B)
but Observer only represents the input type. Nonetheless, in the observe method we are implicitly making
use of the output type B when we bind output to the result of f(in) and then call observe on m’s observers.
36 APPENDIX A. SOLUTIONS TO EXERCISES
Can we even write down a type for output? We can, using a feature called existen al types. An existen al
type represents a specific type that we don’t know. Here the existen al would represent the unknown type B.
Unfortunately there is a compiler bug that means the compiler actually infers Any in this situa on.
Let’s choose a different solu on that doesn’t introduce existen al types and doesn’t trigger this compiler bug.
We’ll introduce a type to represent a transforma on that has both an input and an output—in other words
Observer[A] with EventStream[B]—and define our structural recursion in this type where both input and
output types are available.
This design also allows us to hide our mutable state (the observers) from users of the library. We haven’t seen
access modifiers in Scala yet, so a quick summary is in order. Scala has protected and private modifiers, like
Java, but the meanings are slightly different. The good news is you can forget about the nuance. As we don’t
use tradi onal OO inheritance and overriding very o en in Scala the details of access modifiers aren’t very
important. There is only modifier I have ever used in Scala, and that is private[packageName], which makes
a defini on visible only within the named package. Below I’ve made Node private to the event package.
package doodle.event
package doodle.event
The task here is a bit underspecified, to leave it open to explore alterna ve designs. My design puts this method
on the companion object of EventStream.
object EventStream {
def fromCallbackHandler[A](handler: (A => Unit) => Unit) = {
val stream = new Map[A,A](identity _)
handler((evt: A) => stream.observe(evt))
38 APPENDIX A. SOLUTIONS TO EXERCISES
stream
}
}
The trick is to realise we’ll have to connect the inputs to Join some other way than having Join implement
observe twice for both types A and B. (This won’t work—consider A and B could be the same concrete type.)
Then the implementa on proceeds much as before. Here’s my version, which doesn’t compile but does follow
the pa erns we’ve used so far.
package doodle.event
def updateLeft(in: A) = {
state.l = Some(in)
state.r.foreach { r => this.observe( (in,r) ) }
}
def updateRight(in: B) = {
state.r = Some(in)
state.l.foreach { l => this.observe( (l,in) ) }
}
}
We have inadvertantly veered away from what Scala’s type inference algorithm can handle. The source of the
problem is the use of type variables in Join (specifically the extends Node[(A,B),(A,B)] part.) Here we
are mixing type variables with a concrete type (a tuple), rather than simply passing them up to Node as we have
done with Map and ScanLeft. This more complex construc on is called a generalized algebraic datatype (GADT
for short.)
40 APPENDIX A. SOLUTIONS TO EXERCISES
Scala’s type inference algorithm can’t work out that A and B in Node are equivalent to (A,B) when Node is a
Join. The solu on is to abandon pa ern matching, and implement our structural recursion with polymorphism.
We have a bit more duplica on as a result, but our code actually compiles. This seems a reasonable trade-off.
package doodle.event
seed = output
observers.foreach(o => o.observe(output))
}
}
final case class Join[A,B]() extends Node[(A,B),(A,B)] {
val state: MutablePair[Option[A],Option[B]] = new MutablePair(None, None)
def updateLeft(in: A) = {
state.l = Some(in)
state.r.foreach { r => this.observe( (in,r) ) }
}
def updateRight(in: B) = {
state.r = Some(in)
state.l.foreach { l => this.observe( (l,in) ) }
}
}
Once you have the hang of higher-kinded types you should find this fairly mechanical.
import scala.language.higherKinds
trait Functor[F[_]] {
def map[A, B](fa: F[A])(f: A => B): F[B]
}
trait Scannable[F[_]] {
42 APPENDIX A. SOLUTIONS TO EXERCISES
object ListInstances {
implicit object list extends Functor[List] with Monad[List] with Applicative[List] with Scannable[List]
def map[A, B](fa: List[A])(f: A => B): List[B] =
fa.map(f)
def flatMap[A, B](fa: List[A])(f: A => List[B]): List[B] =
fa.flatMap(f)
def point[A](a: A): List[A] =
List(a)
def zip[A, B](fa: List[A])(fb: List[B]): List[(A, B)] =
fa.zip(fb)
def scanLeft[A,B](fa: List[A])(b: B)(f: (B,A) => B): List[B] =
fa.scanLeft(b)(f)
}
}
object EventStream {
implicit object eventStream extends Functor[EventStream] with Monad[EventStream] with Applicative[Event
def map[A, B](fa: EventStream[A])(f: A => B): EventStream[B] =
fa.map(f)
def point[A](a: A): EventStream[A] =
EventStream.now(a)
def zip[A, B](fa: EventStream[A])(fb: EventStream[B]): EventStream[(A, B)] =
fa.zip(fb)
def scanLeft[A,B](fa: EventStream[A])(b: B)(f: (B,A) => B): EventStream[B] =
fa.scanLeft(b)(f)
}
}
object IdInstances {
type Id[A] = A
implicit object list extends Functor[Id] with Monad[Id] with Applicative[Id] with Scannable[Id] {
def map[A, B](fa: Id[A])(f: A => B): Id[B] =
f(fa)
def flatMap[A, B](fa: Id[A])(f: A => Id[B]): Id[B] =
f(fa)
def point[A](a: A): Id[A] =
a
def zip[A, B](fa: Id[A])(fb: Id[B]): Id[(A, B)] =
(fa, fb)
def scanLeft[A,B](fa: Id[A])(b: B)(f: (B,A) => B): Id[B] =
A.3. TYPE CLASSES AND IMPLICITS 43
f(b,fa)
}
}
It allows us to treat normal values as if they were monads etc. and hence abstract over code that uses “real”
monads / functors / applica ves and code that doesn’t. This o en occurs when code is used in some contexts
where it runs concurrently (e.g. in Future) and in other contexts where it doesn’t.