Algebra Driven Design Sample
Algebra Driven Design Sample
Sandy Maguire
Copyright ©2020, Sandy Maguire
All rights reserved.
Version 1.1.2
Are you quite sure
that all those bells and whistles,
all those wonderful facilities of your so-called
“powerful” programming languages,
belong to the solution set
rather than to the problem set?
EDSGER W. DIJKSTRA
Contents
Foreword 1
Preface 7
1 Overview 10
1.1 Abstraction . . . . . . . . . . . . . . . . . . . . . . . 10
1.2 What is Algebra-Driven Design? . . . . . . . . . . . 13
1.3 Conventions . . . . . . . . . . . . . . . . . . . . . . . 20
1.3.1 Why Haskell? . . . . . . . . . . . . . . . . . . 20
1.3.2 Reading Haskell . . . . . . . . . . . . . . . . 24
1.3.3 Understanding Haskell Types . . . . . . . . . 32
1.3.4 Equational Laws . . . . . . . . . . . . . . . . 35
1.4 A Note on the Companion Library . . . . . . . . . . 40
I Designing Algebras 43
2 Tiles 44
2.1 Basic Building Blocks . . . . . . . . . . . . . . . . . 46
2.2 Subdividing Space . . . . . . . . . . . . . . . . . . . 57
2.3 Observations . . . . . . . . . . . . . . . . . . . . . . 68
2.4 Generalization . . . . . . . . . . . . . . . . . . . . . 74
iv
CONTENTS v
4 Scavenger Hunt 86
4.1 Input Filters . . . . . . . . . . . . . . . . . . . . . . 101
4.2 Simultaneous Challenges . . . . . . . . . . . . . . . . 106
4.3 Challenge Completion . . . . . . . . . . . . . . . . . 113
4.4 Simplification . . . . . . . . . . . . . . . . . . . . . . 121
4.5 A Unified Observation . . . . . . . . . . . . . . . . . 125
4.6 Symmetry . . . . . . . . . . . . . . . . . . . . . . . . 132
4.7 Clues . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
4.8 Generalization . . . . . . . . . . . . . . . . . . . . . 147
Acknowledgements 317
Bibliography 319
Glossary 323
Foreword
𝑥+𝑦 =𝑦+𝑥
and associativity
(𝑥 + 𝑦) + 𝑧 = 𝑥 + (𝑦 + 𝑧)
1
CONTENTS 2
getchar() - getchar() == 0
When an API satisfies a rich set of laws, then the algebra gives
us freedom:
Fortunately, today there are tools for Haskell that can answer
these questions for you—QuickCheck to test that a particular law
is satisfied, and QuickSpec to find a set of laws in the first place. So
we are in a much better position, today, to apply algebraic methods
to our code in practice, than the pioneers I quoted above.
CONTENTS 6
John Hughes
Gothenburg, Sweden, September 2020.
Preface
For the last seven years, I’ve been fascinated by what I see to be
the central question in computing — “why isn’t functional program-
ming more popular?” In my eyes, functional programming (FP) is
easier to get right, requires less effort to produce, and comes with
stronger maintainability guarantees than the more conventional
paradigms afford. But if FP is so fantastic, why hasn’t it taken
over the world yet?
I can see only three possibilities.
The most obvious one is that functional programming simply
isn’t all that much better. But this flies directly in the face of
my experience and that of my colleagues in the FP community.
There are numerous stories about people coming from procedural
paradigms and falling in love with FP; but very few in which peo-
ple go the other direction. Common themes are that functional
programming is more “elegant,” and “easier to reason about,” and
that it “expands our way of thinking.”
Perhaps instead, it’s that the market doesn’t reward what func-
tional programming brings to the table. It seems a little far-fetched,
but maybe software doesn’t live and die by the speed at which it’s
written, its correctness, and its maintainability. By being smaller
than its procedural and object-oriented peers, functional program-
ming languages boast significantly fewer libraries, which is likely
part of the issue. It’s not that the market doesn’t reward speed,
merely that until we achieve library parity, the mainstream inertia
7
CONTENTS 8
will keep its adherents. There is probably some truth to this. But
I don’t think this explains the whole story.
However, the third option is that we FP-people are just not very
good at applying functional principles in large-scale, real-world ap-
plications. That is to say, maybe the problem isn’t with functional
programming; it’s that we collectively aren’t yet good enough with
it. It’s a common argument that functional programming works
well in the small, but in the large, you actually need to deal with
external systems and real-world complexity. It’s in this interaction
with reality that the cracks in FP begin to show.
I think FP’s inability to take over the world is this last point.
I believe that, as a community, we’re not very good at scaling
up functional thinking. There is no blame here; after all, we all
have significantly more experience engineering procedural systems
than we do functional ones. The issue is that it takes a lot of
false starts to move forward. In the mainstream world, these false
starts were already taken by our predecessors. But with functional
programming only now just starting to gain widespread attention,
we stand on our own, with little conventional knowledge to fall
back on.
Fortunately, we’re not alone in this endeavor. This book
presents a fundamentally different approach for thinking about
and writing software, one which plays to our strengths. It’s not a
novel idea by any means — while researching this book, I found
that most of my discoveries were first unearthed in the mid-70s
in the academic community. Academic researchers don’t have the
best track record for communicating their research outside of the
ivory tower, and I fear that’s what has happened here. An idea is
only as good insofar as it can be acted upon. But it’s important
to note that the material presented here is in no way my own
research.
To paraphrase Gwern Branwen: if this book has done anything
meritorious, it was perhaps simply putting more work into than
someone else would have. My goal has always been to help com-
CONTENTS 9
municate better ideas than the ones I come up with. That’s the
natural progression of learning, and after a year and two complete
rewrites of this book, boy have I ever learned a lot. I hope you do
too.
Sandy Maguire
Victoria, BC, Canada
September 2020
Chapter 1
Overview
1.1 Abstraction
This book is about abstractions — how to think about them, find
good ones, and wield them effectively. At first blush, this sounds
like a question of style; certainly, abstraction is abstraction is ab-
straction, right? No, not only is this not right; it is not even wrong.
When asked what abstraction is, many programmers will give one
of the following answers:
10
1.1. ABSTRACTION 11
aren’t wired for it. We aren’t well-adapted for thinking about sub-
tle state interactions that accumulate over hundreds of thousands
of lines of code. Code that is pedantic, written to describe, in excru-
ciating detail, tasks which are self-evident to humans. Computers
are, by and large, idiots, and the vast majority of a software engi-
neer’s job is understanding problems so well that you can explain
them to these uncomprehending machines.
It is this comprehension that is of paramount importance. As
this book argues, the software engineer’s ability to understand prob-
lems is her primary employable skill. The code is merely a byprod-
uct, serving only to explain this understanding to the computer —
uninteresting in its own right.
While it might sound like a truism to suggest we focus on the
understanding of problems rather than the programming of solu-
tions, consider just how difficult this is with conventional tools
and best practices. Our programming languages, the primary tool
for thought for many software engineers, give us no support in this
department. Our only facilities for writing code that is “easily un-
derstood” are to use descriptive variable names, write explanatory
comments, and to optimize our code “to be read” — whatever that
means.
But notice that these are all code-centric improvements. At
their core, they still privilege the program as the fundamental unit
of study — the very thing that is first and foremost an artifact
for a computer to execute. The program is not our understand-
ing of the problem and its solution; it is the end product of that
understanding. If we’re lucky, the program comes with comments
that are simultaneously insightful and true, though ensuring this
continues to be the case over time is probably asking too much.
For better or worse (but mostly just for the worse,) documenta-
tion is our only tool for preserving institutional understanding of
a codebase. Unfortunately, there is no tool in existence to ensure
our documentation stays in sync with the system it purports to
document. Indeed, tools like doctest can help show our examples
1.2. WHAT IS ALGEBRA-DRIVEN DESIGN? 15
produce the output, but there are no tools that check the prose of
our comments.
Of course, the program still exists, and in some sense, is the
source of truth of meaning. When documentation goes stale, we
are not stuck; we can always reverse-engineer understanding from
the program itself. Unfortunately, this state of affairs is almost syn-
onymous with “programming,” at least in most professional spheres.
Over time, software engineers get quite good at this detective work
— sussing out the “what” from the “how” — but it is essential to
remember that this is inherently a lossy procedure. Rather than
being able to page in the original author’s understanding of the
code, we must reinterpret it, reading between the lines. This is
more of an exercise of psychology than of engineering, as the goal
is to recreate another human’s state of mind.
Civilizationally-speaking, we are remarkably successful in this
endeavor of reverse-psychology. It’s a testament to all of the heroic
maintenance programmers out there who manage to keep these
software systems up and running. But let’s not mince words, this
is truly a Herculean undertaking. Programming is a fundamentally
challenging undertaking, yes, but it shouldn’t be nearly as hard as
it is. Nor need it be.
The irony here is in our rush to automate away other profes-
sions by providing better tools, by and large, we’ve forgotten to
apply this same mindset to our own field. Rather than trying
to solve underlying problems, which we are reasonably blind to,
we usually find a convenient workaround. For example, why do
we still represent and edit our source code as strings? Syntacti-
cally valid programs are a vanishingly small subset of all possible
strings. The number of valid edits to a program is minuscule com-
pared to the number of possible ways we can manipulate a string.
Yes, source code does need eventually to be stored as bytes some-
where on a filesystem. But memory too is just a series of bytes,
and unless you’re a low-level C programmer, you almost certainly
never think of memory like that. We don’t manipulate instances
1.2. WHAT IS ALGEBRA-DRIVEN DESIGN? 16
1.3 Conventions
This book uses a few conventions throughout its pages. While it’s
not necessary to discuss the full “hows and whys” here and now —
I’m sure you’re hungry to jump into the thick of Algebra-Driven
Design — we will need to cover the basics.
Designing Algebras
43
Chapter 2
Tiles
44
45
data Tile
haskell :: Tile
sandy :: Tile
When rendered, these tiles look like figure 2.4 and figure 2.5.
All terms in an algebra are built from terminal constructors
(like haskell and sandy above) and inductive constructors: ones
2.1. BASIC BUILDING BLOCKS 47
to the original Tile were not four but instead 𝑛, our rotation must
instead be by 360𝑛 degrees. Thus, we can closely specify what it
is we’re talking about by requiring the following law always holds
true:
Law: "cw/cw/cw/cw"
∀ (t :: Tile).
cw (cw (cw (cw t))) = t
The laws are of critical importance, as they are what foist mean-
ing upon our otherwise empty syntactic constructs.
Let’s return to our algebra. Of course, there is no reason to
privilege one direction of rotation over another. Let’s also provide
a ccw (“counterclockwise”) constructor with the same type, as il-
lustrated in figure 2.9.
We don’t require both cw and ccw — having both gives us no
additional expressiveness than having only one. To see this for
yourself, note that rotating counterclockwise once is equivalent to
rotating clockwise three times, and vice versa. If we valued extreme
parsimony, we could certainly get by having only one of these com-
binators, but we will provide both because they are both useful.
However, having two ways of getting the same result gives us twice
2.1. BASIC BUILDING BLOCKS 51
Law: "ccw/cw"
∀ (t :: Tile).
ccw (cw t) = t
Law: "cw/ccw"
∀ (t :: Tile).
cw (ccw t) = t
2.1. BASIC BUILDING BLOCKS 52
Again, these laws are nothing we have control over; they are
required to hold for any cw and ccw that could correspond to our in-
formal notions that these operations should rotate their arguments
90 degrees. In time, you will learn to analyze software in terms of
which laws it satisfies, that is to say, you will be able to visualize
software via its equations.
Our equations don’t just look like the sort of algebra you did
in grade-school; indeed, we can use them in just the same way. For
example, we can derive the fact that rotating clockwise three times
is equivalent to going counterclockwise once, via simple algebraic
manipulation. We start with the fact that rotating clockwise four
times is equivalent to doing nothing, then use ccw on both sides of
the equation, and then use the fact that ccw eliminates a cw:
ccw t
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "cw/cw/cw/cw")
ccw (cw (cw (cw (cw t))))
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "ccw/cw")
cw (cw (cw t))
Law: "flipH/flipH"
∀ (t :: Tile).
flipH (flipH t) = t
Law: "flipH/cw/cw/flipH"
∀ (t :: Tile).
flipH (cw (cw (flipH t)) = cw (cw t)
Law: "x-symmetry"
∀ (t :: Tile).
flipH (cw t) = ccw (flipH t)
Exercise Find a way of recreating figure 2.11, using only cw, ccw
and flipH.
Of course, flipV is also its own inverse, but two other interesting
equations as well – that we can derive it from cw, ccw and flipH,
and that performing both flips is equivalent to doing two rotations.
Law: "flipV/flipV"
∀ (t :: Tile).
flipV (flipV t) = t
Law: "ccw/flipH/cw"
∀ (t :: Tile).
flipV t = ccw (flipH (cw t))
2.1. BASIC BUILDING BLOCKS 56
Law: "flipV/flipH"
∀ (t :: Tile).
flipV (flipH t) = cw (cw t)
Exercise Derive the fact that flipV is its own inverse, using any
of the other laws we’ve given for our algebra.
Solution
flipV (flipV t)
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "flipV")
flipV (ccw (flipH (cw t)))
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "flipV")
ccw (flipH (cw (ccw (flipH (cw t)))))
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "cw/ccw")
ccw (flipH (flipH (cw t)))
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "flipH/flipH")
ccw (cw t)
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "ccw/cw")
t
Solution
flipV (flipH t)
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "flipV")
ccw (flipH (cw (flipH t)))
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "ccw")
cw (cw (cw (flipH (cw (flipH t)))))
2.2. SUBDIVIDING SPACE 57
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "x-symmetry")
cw (cw (flipH (ccw (cw (flipH t)))))
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "ccw/cw")
cw (cw (flipH (flipH t)))
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "flipH/flipH")
cw (cw t)
Law: "flipH/beside"
Again, rather than make our users jump through hoops, we will
just provide above as its own constructor.
Figure 2.16: above (beside (cw haskell) (cw (cw sandy))) (beside
sandy (ccw haskell))
2.2. SUBDIVIDING SPACE 61
Law: "above"
Law: "above/beside"
quad :: Tile -> Tile -> Tile -> Tile -> Tile
Law: "quad"
Law: "swirl"
∀ (t :: Tile).
quad t (cw t) (cw (cw t)) (ccw t) = swirl t
color
:: Double -- ^ red
-> Double -- ^ green
2.2. SUBDIVIDING SPACE 64
Figures figure 2.20, figure 2.21 and figure 2.22 give some illus-
trations of the different channels, and how alpha blending works.
The color combinator has the interesting property that is un-
affected by cw and flipH:
Law: "cw/color"
Law: "flipH/color"
Law: "opaque"
Law: "transparent"
empty :: Tile
Law: "empty"
Tile Implementation
data Tile a
instance Functor Tile
instance Applicative Tile
quad :: Tile a -> Tile a -> Tile a -> Tile a -> Tile a
swirl :: Tile a -> Tile a
155
5.1. THE INITIAL ENCODING 156
-- etc
Where did these five come from, you might wonder. The an-
swer is that they come from trial and error, and I have saved my
readers from the trial on this particular example. Picking primi-
tives is more of an art than a science; we can allow our intuition
and implementation experience to suggest constructors to use as
primitives. There is little consequence for picking a lousy set of
primitives; if it’s not minimal, you’ll have to do a bit more work.
If it’s insufficient to implement the entire algebra, you’ll get stuck
within five minutes and can then backtrack.
In the case of Tile above, our primitive data constructors cor-
respond with constructors in our algebra, but this is not a require-
5.1. THE INITIAL ENCODING 160
ccw t
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "cw/cw/cw/cw")
ccw (cw (cw (cw (cw t))))
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "ccw/cw")
cw (cw (cw t)))
∀ (t :: Tile).
flipV t
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "rotated flipH")
ccw (flipH (cw t)) ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "cw/cw/cw/cw")
ccw (cw (cw (cw (cw (flipH (cw t))))))
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "ccw/cw")
cw (cw (cw (flipH (cw t))))
This derivation goes all the way to our primitive forms cw and
flipH, which is technically the right way to go about things. How-
ever, you’ll notice that the line of the proof marked by 1 is already
5.1. THE INITIAL ENCODING 162
beside t1 t2
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "above")
ccw (above (cw t1) (cw t2))
quad t1 t2 t3 t4
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "quad")
above (beside t1 t2) (beside t3 t4)
5.1. THE INITIAL ENCODING 163
quad :: Tile a -> Tile a -> Tile a -> Tile a -> Tile a
quad t1 t2 t3 t4 = above (beside t1 t2) (beside t3 t4)
swirl t
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "swirl")
quad t (cw t) (ccw t) (cw (cw t))
fmap f t
= ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ (via "pure/ap")
pure f <*> t
rasterize w h (Ap f a) =
coerce (rasterize' w h f <*> rasterize' w h a)
5.1. THE INITIAL ENCODING 165
This law states that two tiles t1 and t2 are equal if and only
if they rasterize to the same pixels for every imaginable width
and height. Regardless of the actual memory layout for our tiles,
this is the metric by which we’d like to consider two tiles equal.
This notion can be encoded by giving an instance of the Observe
typeclass from the quickspec library.
Observe allows us to describe observational equality between
terms — possibly requiring quantified arguments. As an interface,
it requires us to fill in one function, observe, which has a parameter
for the quantified arguments, and another for the term we’d like
to observe. These arguments are generated randomly via the prop-
erty testing Arbitrary machinery, and two terms require observed
equality for every input thrown at them.
5.2. GENERATING TESTS 173
instance Observe
(Small Int, Small Int) ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1
[[a]] ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 2
(Tile a) ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 3
where
observe (Small w, Small h) t
= rasterize (max 1 w) (max 1 h) t ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 4
look over the results, ensuring ours are present. If everything looks
good, QuickSpec has an option which emits valid Haskell property
tests for each.
Several combinators exist for building QuickSpec signatures,
with the most important of them being con :: String -> a -> Sig.
This signature is used to describing constructors. I like to list my
constructors together in one big signature:
sig_cons :: Sig
sig_cons = signature
[ con "cw" $ cw @A ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ ⋅ 1
, con "ccw" $ ccw @A
, con "beside" $ beside @A
, con "above" $ above @A
, con "flipV" $ flipV @A
, con "flipH" $ flipH @A
, con "pure" $ pure @Tile @A
, con "<*>" $ (<*>) @Tile @A @B
, con "quad" $ quad @A
, con "swirl" $ swirl @A
, con "behind" $ liftC @(Monoid A) $ behind @A ⋅ ⋅ 2
, con "empty" $ liftC @(Monoid A) $ empty @A
]
317
9.2. STRUCTURES 318
319
9.2. STRUCTURES 320
https://doi.org/10.1145/357766.351267.
Kiselyov, Oleg. 2008. “How to Zip Folds: A Library of Fold Trans-
formers (Streams).” 2008. http://okmij.org/ftp/Haskell/zip-
folds.lhs.
Koopman, Pieter, Rinus Plasmeijer, and Jan Martin Jansen. 2014.
“Church Encoding of Data Types Considered Harmful for Im-
plementations: Functional Pearl.” In Proceedings of the 26nd
2014 International Symposium on Implementation and Appli-
cation of Functional Languages - IFL ’14, 1–12. Boston, MA,
USA: ACM Press. https://doi.org/10.1145/2746325.2746330.
Koppel, James. 2018. “You Are a Program Synthesizer.” Strange
Loop. https://www.youtube.com/watch?v=ldkF-4WNZqA.
———. 2019. “The Best Refactoring You’ve Never Heard of.”
Compose NYC 2019. https://www.youtube.com/watch?v
=vNwukfhsOME.
Kutsurua, George. 2014. “Quicksort with Python. Stack Overflow.”
December 13, 2014. https://stackoverflow.com/a/27461889.
Landin, P. J. 1966. “The Next 700 Programming Languages.”
Communications of the ACM 9 (3): 157–66. https://doi.or
g/10.1145/365230.365257.
Leeuwen, J. van, ed. 1990. Handbook of Theoretical Computer
Science. Amsterdam ; New York : Cambridge, Mass: Elsevier
; MIT Press.
Lipovača, Miran. n.d. “Recursion. Learn You as Haskell for Great
Good!” Accessed July 21, 2020. http://learnyouahaskell.com
/recursion.
McBride, Conor. 2001. The Derivative of a Regular Type Is Its
Type of One-Hole Contexts (Extended Abstract).
Nelson, Joe. 2017. “The Design and Use of QuickCheck. Begriffs.”
January 14, 2017. https://begriffs.com/posts/2017-01-14-
design-use-quickcheck.html.
Smallbone, Nicholas, Moa Johansson, Koen Claessen, and Max-
imilian Algehed. 2017. “Quick Specifications for the Busy
Programmer.” Journal of Functional Programming 27: e18.
9.2. STRUCTURES 322
https://doi.org/10.1017/S0956796817000090.
Thatcher, J. W., E. G. Wagner, and J. B. Wright. 1982. “Data
Type Specification: Parameterization and the Power of Specifi-
cation Techniques.” ACM Transactions on Programming Lan-
guages and Systems (TOPLAS) 4 (4): 711–32. https://doi.or
g/10.1145/69622.357192.
Wickström, Oskar. 2019. Property-Based Testing in a Screencast
Editor. Leanpub. https://leanpub.com/property-based-testin
g-in-a-screencast-editor.