← Contents · Runnable Specifications by Eric Normand · Work in progress · Comments
103
103
Chapter 4
Composition Lens Part 1
The operations play a vital role in the domain model, and until
now, weve considered them in isolation. But we rarely do one
operation by itself. Instead, we perform sequences of operations
all operating on the same data. These operations relate to each
other. We can extract quite a lot of information from those rela-
tionships to make better design decisions.
Chapter objectives
Learn the two major approaches to identifying important do-
main concepts.
Discover how the closure property can expand the expres-
sivity of your model.
Understand that most operations are called in sequence with
other operations.
State
UI events
mutations
current state
HTML
present
translate
Super Mega Galactic
by the end of this
chapter, you’ll understand
these three diagrams
Important terms
composition
organic and synthetic
approaches
closure property
algebraic properties
total property
partial property
you’ll learn these
terms in this chapter
104 Chapter 4
The most interesting interactions involve multiple operations
Do you remember the touchscreen interface the barista used to take an order? Let’s look at a typical
interaction the barista will have with a customer.
Here’s the whole sequence written out:
let coffee = newCoffee();
coffee = setSize(coffee, “galactic”);
coffee = setSize(coffee, “mega”);
return value from one is used
as an argument for the next
coffee = setRoast(coffee, “burnt”);
coffee = addAddIn(coffee, “soy”);
coffee = addAddIn(coffee, “almond”);
coffee = removeAddIn(coffee, “almond”);
coffee = removeAddIn(coffee, “almond”);
Operations are rarely used alone. Instead, we use them in sequence and in parallel. A composition
is two or more operations performed where the return value of one operations is used as the argu-
ment of another.
One coee please!
Sure thing! What size?
Galactic.
No, just mega.
Great. Is charcoal okay?
Burnt, please.
Anything in that coee?
Can I add soy? And almond?
Yes!
Sorry, no almond.
You got it!
let coffee = newCoffee();
coffee = setSize(coffee, "galactic");
coffee = setSize(coffee, "mega");
coffee = setRoast(coffee, "burnt");
coffee = addAddIn(coffee, "soy");
coffee = addAddIn(coffee, "almond");
presses “new coffee”
sets size
sets roast
adds soy and almond
removes almond
accidentally touches “remove almond” a second time
sets size again
coffee = removeAddIn(coffee, "almond");
coffee = removeAddIn(coffee, "almond");
Customer Barista Touchscreen
105Composition Lens Part 1
Introducing the Order GUI
We just got the UI mockups from our designers for the touch-
screen interface to enter an entire order. Our job is to build it.
This UI has dierent sections. We can begin to break down the
GUI into those sections to understand its structure. We’re going
to build this GUI in HTML. We could, of course, build this GUI
directly in HTML. But we wont. Instead, were going to use this
example as a learning journey to understand how to nd deeper
structure, model it, and encode it in our language.
Were looking for an intermediate layer between HTML and the
order GUI we are asked to build. This layer is expressed in HTML,
with structure that is more specic to our needs. Then we will
build this specic GUI out of it.
This may be over-engineering if you were solving the same
problem in the real world. But in this book, it will be a useful
learning journey, so lets go!
Super
3 coees
mega, charcoal,
2 soymilk
mega, charcoal,
2 soymilk
mega, charcoal,
2 soymilk
Total: $12.00
$5.00
$5.00
$5.00
Raw
Soymilk
Espresso
Almond
Mega
Burnt
Galactic
Charcoal
+
+
+
+
+
2
1
0 0
0
-
-
-
-
-
Chocolate
Hazelnut
HTML
Order GUI
GUIs in General
looking for
intermediate
structures
106 Chapter 4
What is a GUI?
What is a GUI? A question like this focuses our mind and forces us
to dene the essence of a thing.
There are many answers to the question. And deciding about
right and wrong, truth and falsehood, is the work of philosophers.
Were not so interested in that. We are interested in a number of
things:
1. Leverage
2. Clarity
3. Precision
4. Generality
Precision means that our denition can be concisely stated as
we mean it. Are we able to state the denition in our formal lan-
guage?
Clarity means that our denition is unambiguous. It gives us a
path forward without kicking the can down the road.
Leverage means it is worth the eort. We want to get more
out of it than we put into it. We could hack a GUI together with
spaghetti code and brute force. We want a denition that makes
building the GUI easier. The leverage we seek comes from struc-
tural similarity between the domain and our code.
Finally, the denition must be at the right level of generality.
We dont seek to maximize generality. Instead, we want some-
thing that targets the layer we’ve set up. It needs to be built out of
HTML and able to build our order GUI and similar GUIs in. Being
useful for ALL GUIs is too general. But being useful only for this
particular design is too specic.
Keeping those things in mind, try to come up with a denition.
It’s a useful exercise. You may not get it right the rst try, but its
worth trying. It will jog your thinking.
Write down your denition before you turn the page. On the
next page, youll see my denition.
107Composition Lens Part 1
A model of GUIs
Here is my denition of GUI:
A GUI presents the applications state and available operations
to the user graphically; and it translates the user’s interactions
into operations that modify the state.
Here is a picture:
As the state changes, the presenter generates an HTML repre-
sentation, which the GUI delivers to the browser. The user will
tap the touchscreen (the only kind of user interaction we need),
which generate events. The translator will interpret those events
into mutations, which will be applied to the current state. Then it
goes through the cycle again.
There are a number of concepts we need to dene, which will
form the basis of the architecture of the application.
I will proceed through the denitions of those concepts. Just
remember that there is not one right architecture for web UIs.
This oneis merely practical for teaching a number of concepts.
Here are the concepts and their types and signatures:
type HTML = string;
type UIEvent = string;
function translate<T>(uiEvent) //=> mutation<T>
function mutation<T>(T) //=> T
function present<T>(T) //=> HTML
The GUI architecture will handle everything if we pass it
present() and translate() functions.
State
UI events
mutations
current state
HTML
present
translate
108 Chapter 4
The GUI architecture
Here is the code for the GUI architecture:
let state = {};
let present = (state) => “Hello!”;
let translate = (event) => (state) => state;
const container = document.getElementById(“gui-container”);
container.addEventListener(“click”, (e) => {
constevent=ndEvent(e.currentTarget,e.target);
const mutator = translate(event);
state = mutator(state);
container.innerHTML = present(state);
});
function ndEvent(top, bottom) {
letevent=undened;
for (let e = bottom; e !== top; e = e.parentElement)
if (e.dataset.event)
event = e.dataset.event;
return event;
}
container.innerHTML = present(state);
This is a barebones GUI framework, but it will do the job. The
rst three lines dene the behavior of our specic GUI, the rest of
the lines are the machinery to make click events work.
Because it is an HTML GUI, we need to dene the HTML:
<html>
<body>
<div id=”gui-container”></div>
<script src=”/gui.js”>
</body>
</html>
As it is, this GUI will simply display the text Hello!”. We’ll mod-
ify the three variables at the top (state, present, translate)
iteratively to dene the MegaBuzz order GUI. I suggest you create
these two les and view the HTML le with a browser to test it.
109Composition Lens Part 1
Q&A
Why are we dening our own GUI framework? Can’t we just
use React? Or Vue?
It’s a good question. I’m dening the GUI framework because I
don’t want this book to be about React or Vue. I don’t even want it
to be about HTML, but I had to pick something.
The main idea behind runnable specications is that we can
run them. We needed some kind of system that would actual-
ly run. If youre familiar with React or Vue or any other way of
building GUIs, please feel free to use one of those.
However, be careful. When using a practical framework, it is
easy to slip into implementation mode, which we are trying to
avoid. This simple framework exactly models our GUI model. It
has exactly three degrees of freedom (initial state, presenter, and
translator). And each of them has very well-dened inputs and
outputs. These constraints help us stay in specication mode.
110 Chapter 4
A counting GUI
Before we get complicated, let’s understand how this GUI system
works by creating a simple counter. There will be a number, and
every time you click it, it increments by 1.
The rst thing is to dene the initial state.
let state = {count: 0};
Then we implement the presenter. We’ll return a div containing
the number. The div will have a data-event attribute that will
be picked up by ndEvent().
let present = function (state) { //=> HTML
return `<div data-event=”increment”>${state.count}</div>`;
};
Finally, we implement the translator, which takes an event and
returns a mutation function to be applied to the state. Well de-
ne it in a functional way, using a modied copy.
let translate = function (event) { //=> function (state) => state
if(event === “increment”)
return function (state) {
return Object.assign({}, state, {count: state.count + 1});
};
return state => state;
};
Give the GUI a try. Click the number and it should increment each
time.
0 1 2 . . .
click click click
111Composition Lens Part 1
Displaying the state
As Im developing the GUI, there are times when I want to know
the state as raw as possible. There could be a bug in the presenter,
and I want to see the raw state and the HTML from the presenter.
A simple way to display the state is to output it as JSON at the bot-
tom of the GUI.
let present = function (state) { //=> HTML
return `<div data-event="increment">${state.count}</div>` +
stateViewer(state);
};
function stateViewer(state) { //=> HTML
return`<pre><code>${JSON.stringify(state,undened,2)}</code></pre>`;
}
I also want to see the last event I triggered to make sure things
are working as I expect. I’ll add that to the translate function.
let translate = function (event) { //=> function (state) => state
if(event === "increment")
return function (state) {
return Object.assign({}, state, {count: state.count + 1,
lastEvent: event});
};
return state => Object.assign({}, state, {lastEvent: event});
};
The GUI should look like this:
I hope this simple example shows that we have indeed encoded
our abstraction of the GUI and that it gives us the exibility we
need to build our screen. If thats still not obvious, it will become
so as we continue.
0 1 2 . . .
click
click
click
{
count: 0
}
{
count: 1,
lastEvent: "increment"
}
{
count:2,
lastEvent: "increment"
}
112 Chapter 4
Building the basic elements
We’ve got our basic GUI framework in place. We have a very sim-
ple GUI that displays the state and accepts user input to change
the state. But our GUI looks nothing like what we need it to.
We could build our Order GUI directly out of what we already
have: The GUI Framework and HTML. The GUI Framework de-
nes the types of the presenter and the translator. HTML denes
how it will display in the browser.
However, the number of structural concepts in our GUI is
quite limited and repeated. You might already be able to see that.
But the structural concepts of HTML are abundant. We will want
to take advantage of that and add a GUI Elements layer between
the Order GUI and the GUI Framework.
Those GUI elements dene more specic concepts that are less
applicable to all apps and more applicable to our app. The reduc-
tion in generality is one of the reasons it can give us leverage. But
only if we nd concepts that help us build the GUI we want.
HTML
HTML
GUI Framework
GUI Framework
GUI Elements
Order GUI
Order GUI
113Composition Lens Part 1
Organic and Synthetic approaches
There are two main approaches to encoding a domain. We will re-
fer to them as “organic” and “synthetic. In the organic approach,
you begin by coding the Order GUI with what you already have.
As you recognize regularities in the code, you begin to factor
those out into reusable parts. Those parts form a lower layer.
The synthetic approach is the complementary process. You
rst identify regularities in the domain, then encode those as
concepts in your model in the layer. Then you build the Order GUI
out of those. Both approaches have merit. Both have negatives.
The organic approach is good for exploring a domain where
the structure is not apparent to you. By diving in and coding, you
are able to make forward progress. And you oen see the struc-
ture later as you are coding. We commonly discover structure
due to repeating patterns. But we can also nd it by encountering
diculties. Things that are harder than they should be probably
mean you’ve got the structure wrong.
The synthetic approach doesnt start with the code. It starts
with an analysis of the domain. You look for repeated patterns
and important structures. You develop an understanding of those,
then you encode it in your programming language. The synthetic
approach is good when the domain is well-understood (such as
accounting) and you have a way to explore it, such as access to an
expert (an accountant).
The synthetic approach is oen misunderstood. The carica-
ture of it is that a programmer, with little understanding of a do-
main, on their own begins dreaming up abstractions, then cod-
ing those. But this is not how it works. It takes disciplined study
and skilled analysis before coding happens.
The truth is that we code in both modes alternatively. Both do-
main analysis and exploratory coding are great sources of infor-
mation. This is why we model in code and seek rich feedback. We
might analyze the domain, encode what we discover, then nd
that there was something we missed, which we explore organi-
cally.
114 Chapter 4
Discovering the structure of our GUI
Lets take a close look at our GUI and uncover the structures with-
in. I’ll mark up the image and discuss each structural pattern.
We see several repeated elements and patterns:
text
image
border
frame
vertical layout
horizontal layout
With these six components, we can build this GUI. Lets get to
work on the next page.
Super
3 coees
mega, charcoal,
2 soymilk
mega, charcoal,
2 soymilk
mega, charcoal,
2 soymilk
Total: $12.00
$5.00
$5.00
$5.00
Raw
Soymilk
Espresso
Almond
Mega
Burnt
Galactic
Charcoal
+
+
+
+
+
2
1
0 0
0
-
-
-
-
-
Chocolate
Hazelnut
vertical layout
horizontal layout
border
image
text
frame
115Composition Lens Part 1
What is a component?
The rst thing we need to decide is, What is a component? We
are building at a certain layer in the stack. We are building on top
of the GUI framework, which only gives us three things to dene:
the structure of the state — T
the presenter — function present<T>(T) //=> HTML
the translator — function translate<T>(uiEvent) //=> mutation<T>
We will these components within the GUI Elements layer:
text
image
border
frame
vertical layout
horizontal layout
Then we can build our GUI using those in the Order GUI layer.
But that brings us to a tough quesion: What is a component? Lets
try this denition on for size: A component takes some part of the
state, renders it to HTML, and can be composed with other com-
ponents into a presenter. It will have this type signature:
function component<T>(T) //=> HTML
Notice that that is the same type signature as presenter. This is
actually a very good thing because it means that renering a par-
tial state is no dierent from rendering the full state. We can vi-
sualize each component separately using the same framework.
And if we get the composition right, those will also build up into
presenters, so we can see partial GUIs using the same framework.
HTML
GUI Framework
GUI Elements
Order GUI
these pieces are
given to us by the GUI
Framework
116 Chapter 4
Complex operations rst
We know what components we need, but where do we start? We
could start with the simple ones, like text, because then we can
knock them out before we go to the harder ones. But I have found
through experiences that it is always better to start with the com-
plex ones.
When we dene the complex operations, we immediately face
the most dicult problems. Solving those problems gives us a lot
of useful information about how the pieces work together. And
that information usually results in us having to rework some-
thing. Maybe the function needs a new argument, or perhaps the
return type needs to be augmented.
In any case, any code we’ve written will have to be revisited at
the least and rewritten at the worst. If weve written the simple
ones rst, all of that work could be scrapped.
Another way to look at it is that the complex operations have
more interesting properties. By dening them and exploring the
properties, the complex operations reveal design constraints that
help us make design decisions in other parts of the code. We have
more information. And so then we can make better decisions
elsewhere.
Contrast this with what I was taught to do, which was to cre-
ate a class, add some instance variables, then immediately tackle
the easiest methods: the getters and setters. By the time I got to
the more complex operations, I had a much better idea of what
instance variables I should even have. I would either go back and
replace the wrong instance variables or leave them, creating a
mess. Complex operations are harder, but they contain more in-
formation.
The most complex operations are typically the combination
operations. They take two or more things that are typically al-
ready complex and combine them into an even more complex
thing. In our case, the two combination operations are the hori-
zontal and vertical layout functions. These will take two or more
things and lay them out next to each other in the corresponding
direction. Lets get to those rst.
117Composition Lens Part 1
Horizontal and vertical layout
The horizontal layout simply means two or more components
will be put next to each other horizontally, like so:
This means we know some of the arguments to the constructor
function:
function horizontal(components) //=> ??
We will pass an array of components (which have the type sig-
nature of a presenter) as the argument. What should the return
value be? I suggest it should be a presenter as well. That way, we
can present what horizontal() produces.
function horizontal(components) //=> presenter
Here our notation is failing a little, so I’m going to stop calling
them components and use the same name for both. The second
name is not helping.
function horizontal(presenters) //=> presenter
The function horizontal() will take an array of presenters, will
package them up , and will return a presenter. This is an import-
ant property and we’ll explore it in detail shortly.
Vertical layout will look very similar:
function vertical(presenters) //=> presenter
There is one more thing that we need to add, which is that both
of these presenters could be clickable. We need to provide an op-
tional event name:
function horizontal(presenters, event) //=> presenter
function vertical(presenters, event) //=> presenter
Please note that vertical() and horizontal() take presenters
and return presenters. We’ll see why this is important soon.
118 Chapter 4
The other presenters
Writing the function signatures for the other presenters should
be pretty straightforward. We will continue to write them from
the most complex to the least complex.
Frame and border are a kind of mutation function. They take a
presenter and draw either a dark box (frame) or a light, rounded
box (border) around it.
function frame(presenter) //=> presenter
function border(presenter) //=> presenter
Text and image are simple constructor functions for presenters.
They also need to be clickable, so we pass an event.
function text(string, event) //=> presenter
function image(url, event) //=> presenter
119Composition Lens Part 1
Implementation of horizontal and vertical layouts
With all of the function signatures written, we should look for
some that can be dened in terms of the other. I dont think we
have any. Each is rather primitive. Therefore, its time to move
onto implementation.
Again, we want to implement the more complex ones rst,
which are the horizontal and vertical layout presenters. These
operations are combining operations. Here is my implementa-
tion:
function horizontal(presenters, event) { //=> presenter
return function(state) { //=> HTML
let children = presenters.map((p) => p(state));
return `<div ${event?`data-event=”${event}”`:””}
style=”display:ex;
ex-direction:row;
align-items:ex-end;”
>
${children.join(“”)}
</div>`;
};
}
The returned presenter renders all of the children, then wraps
them in a ex-box div, which will lay out the children horizontal-
ly. We can write a similar function for the vertical layout:
function vertical(presenters, event) { //=> presenter
return function(state) { //=> HTML
let children = presenters.map((p) => p(state));
return `<div ${event?`data-event=”${event}”`:””}
style=”display:ex;
ex-direction:column;
align-items: center;”
>
${children.join(“”)}
</div>`;
};
}
120 Chapter 4
Implementation of frame and border
The next two operations to work on are the mutation function,
frame and border.
function frame(presenter) //=> presenter
function border(presenter) //=> presenter
They each take a presenter and return a presenter. Frame adds
a dark rectangle around the presenter. And border adds a light
rounded rectangle around the presenter. Lets start with frame.
function frame(presenter) { //=> presenter
return function(state) { //=> HTML
return`<divstyle="border:1pxsolidblack;
padding: 1em;"
>${presenter(state)}
</div>`;
};
}
They each take a presenter and return a presenter. Frame adds
a dark rectangle around the presenter. And border adds a light
rounded rectangle around the presenter. Lets start with frame.
function border(presenter) { //=> presenter
return function(state) { //=> HTML
return`<divstyle="border:1pxsolidgrey;
border-radius: 1em;"
>${presenter(state)}
</div>`;
};
}
121Composition Lens Part 1
Implementation of text and image
The nal two operations, text and image, are constructors. They
take a conguration and return a presenter.
function text(string, event) //=> presenter
function image(url, event) //=> presenter
Text displays a small bit of text. Its quite simple:
function text(string, event) { //=> presenter
return function(state) { //=> HTML
return `<div ${event?`data-event="${event}"`:""}
style="text-align:center;"
>
${string}
</div>`;
};
}
Image simply displays an image, given its URL.
function image(url, event) { //=> presenter
return function(state) { //=> HTML
return `<img src="${url}"
alt=""
${event?`data-event="${event}"`:""}
>`;
};
}
These two constructors are our simplest operations. And now
were done with a rst, synthetic pass. Remember, the synthetic
approach analyzes the domain for repeated patterns, abstracts
them, then encodes them into code.
Now that weve extracted what we can, we should put it to
the test with an organic pass. In the organic approach, we build
something with the pieces we have, then look for repeated pat-
terns in the code, abstract them, and encode them. Let’s build our
GUI using what we’ve got.
122 Chapter 4
Encoding the size buttons incrementally
Were going to encode the size buttons a small piece at a time.
Here is the ideal design with its parts labeled with the concepts
we’ve developed.
I want to simulate on these pages what it is like when you can run
code quickly to visualize the code as it evolves. It might seem like
a waste of space, but don’t worry, there’s always more space.
As much as possible within the constraints of the page, I’m go-
ing to show the execution of the code (the image it produces) next
to the code that produces it. Each page will add a little bit more
until weve completed the whole part. This will simulate what it
is like to have your code auto reloaded as you modify it. For in-
stance, I’ve oen programmed with my code editor on the le of
my screen and a web browser on the right. As I change the code,
the browser automatically shows the result of running the code
(a web page).
As we develop the code specication of our GUI, we can run it
and see it, and therefore evaluate it against the design.
Lets get started on the next page.
Super Mega Galactic
image
text
border
vertical
horizontal
123Composition Lens Part 1
Starting with size images with labels
We start with just a tiny piece, the image of the super size.
Editor
let presenter = image(
“https://mega.buzz/super.png”
);
Then we add the label below it and make it generate an event.
Editor
let presenter = vertical([
image(“https://mega.buzz/super.png”),
text(“Super”)
], “size—super”);
Now we make all three sizes, laid out horizontally.
Editor
let presenter = horizontal([
vertical([
image(“https://mega.buzz/super.png”),
text(“Super”)
], “size—super”),
vertical([
image(“https://mega.buzz/mega.png”),
text(“Mega”)
], “size—mega”),
vertical([
image(“https://mega.buzz/galactic.png”),
text(“Galactic”)
], “size—galactic”)
]);
There are a couple of things missing. But before we get to those, I
feel a little disconnected from the state. We are generating events,
but I can’t see them. Lets x that.
Browser
Browser
Super
Browser
Super Mega Galactic
124 Chapter 4
Displaying the state using JSON
We are generating events, yet nothing is happening on the screen.
This is disconcerting. I never want to go too long without seeing
the result of changes. We want to know that the changes we make
to code are having the eect we want so that we maintain fast
feedback.
There’s a simple solution in web programming: We display the
state as JSON in the GUI as part of the presenter. Heres my take
on it:
Editor
let stateViewer = function(state) { //=> HTML
return `<pre><code>${
JSON.stringify(state,null,2)
}</code></pre>`;
}
let coffeePresenter = horizontal([
vertical([
image("https://mega.buzz/super.png"),
text("Super")
], "size—super"),
vertical([
image("https://mega.buzz/mega.png"),
text("Mega")
], "size—mega"),
vertical([
image("https://mega.buzz/galactic.png"),
text("Galactic")
], "size—galactic")
]);
let presenter = vertical([
coffeePresenter,
stateViewer
]);
Now we can see the state.
{}
Browser
Super Mega Galactic
125Composition Lens Part 1
Developing a basic translator
Now we can work on the state and translator. Let’s add an entire
coee to the state, with default size and roast:
Editor
let state = {
coffee: {
size: “super”,
roast: “burnt”,
addIns: {}
}
};
Before we write the entire translator, we can write a simple trans-
lator that records the last event to the state. That way, we can be-
gin seeing the eect sooner. Yay feedback!
Editor
let translator = function(event) { //=> mutator
return function(state) { //=> state
return Object.assign({}, state, {
lastEvent: event
});
};
}
Now when we click on a coee, we can see some eect! We can
test all three coees to make sure the right event is being sent.
We now have a complete loop in our architecture. Changes to
the state are reected on our GUI. Its now time to wire up the
coee state change to the events.
Browser
{
coffee: {
size: "super",
roast: "burnt",
addIns: {}
}
}
Super Mega Galactic
{
coffee: {
size: “super”,
roast: “burnt”,
addIns: {}
},
lastEvent: “size—mega”
}
Super Mega Galactic
Browser
126 Chapter 4
Making the translator change the state
Weve got our basic translator adding the last event to the state
so we can see it. That let us check that our events were working
properly. Now we we handle each of the events. Here is my take
on the translator.
Editor
let translator = function(event) { //=> mutator
let [attribute, ...args] = event.split("—");
let mutator = function(state) { return state; }
if(attribute === "size")
mutator = function(state) { //=> state
return Object.assign({}, state, {
coffee: setSize(state.coffee, args[0])
});
};
return function(state) { //=> state
return Object.assign({}, mutator(state), {lastEvent: event});
};
};
{
coffee: {
size: “super”,
roast: “burnt”,
addIns: {}
},
lastEvent: “size—super”
}
Super Mega Galactic
Browser
{
coffee: {
size: “mega”,
roast: “burnt”,
addIns: {}
},
lastEvent: “size—mega”
}
Super Mega Galactic
Browser
{
coffee: {
size: “galactic”,
roast: “burnt”,
addIns: {}
},
lastEvent: “size—galactic”
}
Super Mega Galactic
Browser
127Composition Lens Part 1
Adding the border for current size
It’s now time to add the border around the current size. We will
need at least one conditional to to implement it, yet we don’t have
any components that make conditionals. Lets build a highlight
component that contains that conditional.
function highlight(presenter, predicate) { //=> presenter
let highlighted = border(presenter);
let unhighlighted = presenter;
return function(state) { //=> HTML
if(predicate(state))
return highlighted(state);
else
return unhighlighted(state);
};
}
highlight() takes a presenter and a predicate (a function of the
state). We make a highlighted version of the predicate by wrap-
ping the presenter in a border. The unhighlighted one is just the
presenter by itself. Inside of the returned presenter, we call the
predicate on the current state. If it returns true, we call the high-
lighted version, otherwise we call the unhighlighted version.
We can use it like this:
let coffeePresenter = highlight(
vertical([
image("https://mega.buzz/super.png"),
text("Super")
], "size—super"),
(state) => state.coffee.size === "super"
);
Super
Super
128 Chapter 4
Showing all three size buttons
Here is the the code for all three size buttons.
Editor
let coffeePresenter = horizontal([
highlight(
vertical([image("https://mega.buzz/super.png"),text("Super")],
"size—super"),
(state) => state.coffee.size === "super"
),
highlight(
vertical([image("https://mega.buzz/mega.png"),text("Mega")],
"size—mega"),
(state) => state.coffee.size === "mega"
),
highlight(
vertical([image("https://mega.buzz/galactic.png"),text("Galactic")],
"size—galactic"),
(state) => state.coffee.size === "galactic"
)
]);
{
coffee: {
size: “super”,
roast: “burnt”,
addIns: {}
},
lastEvent: “size—super”
}
Super Mega Galactic
Browser
{
coffee: {
size: “mega”,
roast: “burnt”,
addIns: {}
},
lastEvent: “size—mega”
}
Super Mega Galactic
Browser
{
coffee: {
size: “galactic”,
roast: “burnt”,
addIns: {}
},
lastEvent: “size—galactic”
}
Super Mega Galactic
Browser
129Composition Lens Part 1
Finding repeated code patterns
It is no surprise that the code for the three size buttons is very
similar. We could use the organic approach to encode that repeat-
ed structure into the model. Lets start with an obvious pattern:
the labeled image.
Editor
let coffeePresenter = horizontal([
highlight(
vertical([image("https://mega.buzz/super.png"),text("Super")],
"size—super"),
(state) => state.coffee.size === "super"
),
highlight(
vertical([image("https://mega.buzz/mega.png"),text("Mega")],
"size—mega"),
(state) => state.coffee.size === "mega"
),
highlight(
vertical([image("https://mega.buzz/galactic.png"),text("Galactic")],
"size—galactic"),
(state) => state.coffee.size === "galactic"
)
]);
Super Mega Galactic
Browser
Design
Raw Burnt Charcoal
Espresso
Hazelnut
labeled images
appear frequently
in the design
It’s a good idea to double-check
with the domain that it is a val-
id concept. In this case, it is re-
peated so much in the design,
we know we have found some-
thing good.
130 Chapter 4
Extracting a labeled image
The goal of the organic approach is to nd new domain structures
by looking at patterns in the code. We found a common pattern,
we veried it in the domain (the GUI design), so lets encode it.
function labeledImage(URL, label, event) {
return vertical([
image(URL),
text(label)
], event);
}
Note: This is a denition, not an implementation. Thats a great
sign that we’ve discovered important ideas in the domain.
Lets encode our coffeePresenter again using this new
function.
let coffeePresenter = horizontal([
highlight(
labeledImage(“https://mega.buzz/super.png”, “Super”, “size—super),
(state) => state.coffee.size === “super”
),
highlight(
labeledImage(“https://mega.buzz/mega.png”, “Mega”, “size—mega”),
(state) => state.coffee.size === “mega”
),
highlight(
labeledImage(“https://mega.buzz/galactic.png”,“Galactic”,
“size—galactic”),
(state) => state.coffee.size === “galactic”
)
]);
131Composition Lens Part 1
Q&A
There’s a lot more repetition, including the call to
highlight(). Why dont we include that in our new func-
tion?
That’s a good question. We certainly have the choice. If we were
trying to eliminate all repeated structure, that would certainly
be the way to go. But that is not our goal. Our goal is to nd im-
portant structure in the domain by using repeated code as a clue.
When we look at the design, we see that the labeled image (an
image with related text below it) is repeated even when it is not
highlighted. So the labeled image, without highlighting, is im-
portant.
Another way to look at it is that our goal is to nd the small-
est structure we can assign a meaningful name to. A meaningful
name is an important sign that it is part of the domain.
But also, dont worry. Theres more refactoring to do. The or-
ganic approach will yield more structure.
132 Chapter 4
Padding
One thing that is bothering me is that there is not enough padding
around the labeled image. The highlight is much too close.
Super Mega Galactic
Browser Design
Super Mega Galactic
I propose that we need a padding component that wraps another
component in whitespace. Here we are not looking at repeated
code (organic approach). What is happening is we are comparing
the encoding (as it results in a GUI) to the domain and deciding
we need to go back to the synthetic approach to nd a new do-
main concept.
Here’s a rst crack at it:
function pad(presenter) { //=> presenter
return function(state) { //=> HTML
return`<divstyle=”padding:10px”>${presenter(state)}</div>`;
}
}
Now we can wrap the labeled image in padding under the high-
light:
let superPresenter = highlight(
pad(
labeledImage(
“https://mega.buzz/super.png”,
“Super”,
“size—super”
)
),
(state) => state.coffee.size === “super”
);
Super
133Composition Lens Part 1
Border jumping
There’s one more thing bothering me with it that is hard to ex-
plain. But Ill try. When I click on the dierent sizes, things jump
around a tiny bit. Maybe one or two pixels. It doesnt block the
functionality, but it’s visually disturbing.
Whats happening is that the 1-pixel border around the labeled
image is appearing and disappearing, changing the position of
the elements within. A solution is to add an invisible 1-pixel bor-
der instead of no border in the highlight.
function whiteBorder(presenter) { //=> presenter
return function(state) { //=> HTML
return`<divstyle=”border:1pxsolidwhite;
border-radius: 1em;”
>${presenter(state)}
</div>`;
};
}
function highlight(presenter, predicate) { //=> presenter
let highlighted = border(presenter);
let unhighlighted = whiteBorder(presenter);
return function(state) { //=> HTML
if(predicate(state))
return highlighted(state);
else
return unhighlighted(state);
};
}
And thisxes it! We are moving fast by being able to visualize our
changes quickly.
Mega
Super Mega Galactic
Super Mega Galactic
134 Chapter 4
Refactoring click events
Id like to do one more refactoring before we summarize what’s
going on. The nal thing I see is that we’re duplicating our event
code in every element. If an element should be clickable, we pass
it an event name, which sets a data-event attribute on it. We can
be more targeted and make the clickability another thing we can
wrap our components with.
Here is our existing code:
function image(url, event) { //=> presenter
return function(state) { //=> HTML
return `<img src="${url}"
alt=""
${event?`data-event="${event}"`:""}
>`;
};
}
function text(string, event) { //=> presenter
return function(state) { //=> HTML
return `<div ${event?`data-event="${event}"`:""}
style="text-align:center;"
>
${string}
</div>`;
};
}
We can extract this into a new component:
function clickable(presenter, event) { //=> presenter
return function(state) { //=> HTML
return `<div data-event="${event}">
${presenter(state)}
</div>`;
};
}
Ill leave the rest of the refactoring (removing events from indi-
vidual components) to you.
You can remove event handling
from these components:
image()
text()
horizontal()
vertical()
labeledImage()
135Composition Lens Part 1
Using clickable()
Now that we have clickable, we can remove the event arguments
and logic from text(), image(), vertical(), horizontal(),
and labeledImage(). Here is what the coee presenter now
looks like using clickable().
let coffeePresenter = horizontal([
clickable(
highlight(
labeledImage("https://mega.buzz/super.png", "Super"),
(state) => state.coffee.size === "super"
),
"size—super"
),
clickable(
highlight(
labeledImage("https://mega.buzz/mega.png", "Mega"),
(state) => state.coffee.size === "mega"
),
"size—mega"
),
clickable(
highlight(
labeledImage("https://mega.buzz/galactic.png","Galactic"),
(state) => state.coffee.size === "galactic"
),
"size—galactic"
)
]);
Theres plenty more repeated structure that we can encode, but
I’ll leave that to you as well.
Now I want to talk about what has made all of this possible.
136 Chapter 4
Fit, layers, and closure: The secrets to expressivity
We built a set of GUI components that work well together. They
allow us to build up larger interfaces from small parts. However,
this chapter is not about the GUI. Its about the deeper principles
that allow the GUI to be expressed.
There are three things important aspects to point out:
1. Concepts with high t
2. Layered (stratied) design
3. Closure property
These three things combined give us a lot of leverage over writ-
ing everything directly in HTML. Ultimately, HTML is all that is
rendered. But the intermediate structures we discovered means
we can:
1. Write the code more easily
2. Read the code more easily
3. Modify the code more easily
4. Experiment more quickly
Lets see how each aspect helps us develop leverage.
Concepts with high t
We used the synthetic and organic approaches to make sure the
concepts in our code corresponded to concepts in the domain. In
the synthetic approach, we directly analyzed the domain for im-
portant structures, then encoded them. In the organic approach,
we found repeated structure in our code, looked for a correspond-
ing structure in the domain, then decided it was important.
In both cases, we wanted the t between the structure in the
domain and the structure in the code to be as similar as possi-
ble. That is, to have a high t. Similarity of structure means they
have the same properties. For example, the design of the GUI
(the domain) had size buttons arranged horizontally. We created
a combination function (the encoding) that lays out components
horizontally. These two structures are in entirely dierent media,
yet they share structure.
137Composition Lens Part 1
Stratied Design
Stratied design means we build the encoding in layers. Each lay-
er uses what’s in the layers below it to dene new meaning to be
used by the layers above it. I discuss stratied design at length in
Grokking Simplicity, chapters 8 and 9.
Stratied design gives you leverage because each layer gives
you a place to encode the important structures from your domain.
Each of those structures gives you leverage by naming a repeated
structure for reuse. As long as you are encoding domain concepts
with high t, each layer gives you a much richer language for de-
ning your GUI.
Closure property
The closure property is a simple idea from mathematics with a lot
of power. Put simply, an operation has the closure property when
its arguments are of the same type as the return value. All of our
mutation and combination functions have the closure property.
function horizontal(presenters) //=> presenter
function vertical(presenters) //=> presenter
function clickable(presenter, event) //=> presenter
function pad(presenter) //=> presenter
function border(presenter) //=> presenter
The closure property is powerful because it allows for arbitrary
nesting. horizontal() can take presenters created in any way,
including by hand-coding a function of the right type, or by com-
posing a presenter from other presenters. horizontal() does
not care how you make the presenter.
Whats more, horizontal() returns a presenter, so it can be
used as an argument to any of the other functions. We can nest
to arbitrary depth, in arbitrary ways. Once we are in the land of
presenters, there are an innite number of things we can do with
that presenter.
Compare this to what it would have looked like if we had made
the functions return dierent types. What if we had organized
them into primitive components, modied components, and lay-
out components, each with incompatible types?
138 Chapter 4
Semantic equivalence of expressions
Now, it turns out that our GUI components are so expressive, we
can say the same thing in dierent ways. This could be a problem.
Remember in our add-ins example, these two arrays represent
the same thing:
["soy", "almond"]
["almond", "soy"]
We had to solve this problem with a normalization function so
that we could make them the same in order to compare them.
However, now we’re not talking about two equivalent pieces of
data. We’re talking about two operations in composition, like so:
coffee.addAddIn("soy").addAddIn("almond")
coffee.addAddIn("almond").addAddIn("soy")
We want to say that these two are equivalent. Whats more, we
want to allow these two expressions because the customer might
tell us what add-ins she wants in arbitrary order. So the barista
must be able to click the buttons in whatever order he wishes.
Similarly, there are things we can express in our GUI compo-
nents layer that don’t make perfect sense, yet they do work. For
instance, these two are equivalent:
clickable(clickable(presenter,"never-res"),"my-event")
clickable(presenter, "my-event")
If I wrap a clickable with another clickable, the last wrap wins
because of the way weve dened bubbling of events. It doesnt
make sense to write the rst line (it has the same eect as the sec-
ond line), yet we must allow it in order to make the functions total.
clickable(clickable(presenter, a), b)
clickable(presenter, b)
We cant use _.isEqual() or any other code to express they are
the same. They dont generate the same presenters, and the pre-
senters dont even generate the same HTML. But the eect of a
click is the same and their visual rendering is the same. There-
fore, we say they are semantically equivalent.
139Composition Lens Part 1
Conclusion
The composition lens gives us the tools to reason about how oper-
ations work in composition—while still separating specication
from implementation. We learned how to expand the expressivi-
ty of a model by leveraging the closure property. Finally, we saw
two complementary approaches, synthetic and organic, that help
us identify domain concepts, and how feedback can speed that
process.
Summary
We rarely call one operation by itself. Instead, we call multi-
ple operations in sequence and in parallel, where the return
value of one call becomes the argument for another. This is
called composition.
A function has the closure property if it returns values of the
same type as one or more of its arguments. The closure prop-
erty exponentially increases the possible combinations of a
set of operations by allowing arbitrary nesting.
The organic approach builds functionality rst, then looks
for important patterns. The synthetic approach analyzes a
domain rst, looking for important patterns. Both approach-
es are valuable ways of understanding a domain.
We sometimes write code to improve the feedback loop. This
code is not destined for production and it doesn’t relate to the
domain.
Up next . . .
Weve learned how to improve the expressivity of a composition
of operations using the closure property. There are many other
properties we can borrow from mathematicians that can improve
the exibility of our code in other ways. In the next chapter, we’re
going to look at some of those properties and how we can apply
them to our code.