gdritter repos documents / 0e4f5a8
Miscellaneous, generally scattered personal documents Getty Ritter 8 years ago
37 changed file(s) with 4670 addition(s) and 0 deletion(s). Collapse all Expand all
1 all: duality.pdf
2
3 duality.pdf: duality.tex
4 pdflatex duality.tex
5
6 clean:
7 rm -rf duality.aux duality.log duality.pdf
1 \documentclass{scrartcl}
2 \usepackage[letterpaper]{geometry}
3 \usepackage{fullpage}
4 \usepackage{cmbright}
5 \usepackage[usenames,dvipsnames]{color}
6 \usepackage{listings}
7 % \newcommand{\both}[2]{\begin{tabular}{ p{4in} p{4in} } #1 && #2 \\ \end{tabular}}
8 \newcommand{\pos}[1]{\textcolor{BrickRed}{#1}}
9 \newcommand{\postt}[1]{\textcolor{BrickRed}{\texttt{#1}}}
10 \newcommand{\dual}[1]{\textcolor{NavyBlue}{#1}}
11 \newcommand{\dualtt}[1]{\textcolor{NavyBlue}{\texttt{#1}}}
12 \newenvironment{both}
13 { \begin{tabular}{ p{3in} p{3in} } }
14 { \\ \end{tabular} }
15
16 \renewcommand{\ttdefault}{pcr}
17 \lstset{basicstyle=\ttfamily,columns=flexible,language=[LaTeX]TeX,%
18 texcl,%set comments in an extra line
19 mathescape=false,escapechar=|,
20 literate={data}{\bfseries\textcolor{BrickRed}{data }}1
21 {codata}{\bfseries\textcolor{NavyBlue}{data }}1
22 {record}{\bfseries\textcolor{NavyBlue}{record }}1
23 {case}{\bfseries\textcolor{BrickRed}{case }}1
24 {of}{\bfseries\textcolor{BrickRed}{of }}1
25 {merge}{\bfseries\textcolor{NavyBlue}{merge }}1
26 {from}{\bfseries\textcolor{NavyBlue}{from }}1
27 {if}{\bfseries{if }}1
28 {then}{\bfseries{then }}1
29 {else}{\bfseries{else }}1
30 {assert}{\bfseries{assert }}1
31 {<+>}{{+ }}1
32 {+}{\textcolor{BrickRed}{+ }}1
33 {*}{\textcolor{NavyBlue}{* }}1
34 {<pos>}{\color{BrickRed}}1
35 {</pos>}{\normalcolor}1
36 {<dual>}{\color{NavyBlue}}1
37 {</dual>}{\normalcolor}1
38 ,%
39 texcsstyle=*\color{blue!70}\bfseries,
40 texcs={end,begin,documentclass}
41 }
42 \begin{document}
43 \section{\pos{Data} and \dual{Codata}}
44
45 \begin{both}
46 \pos{Data} is defined by its \pos{introduction} rules.
47 & \dual{Codata} is defined by its \dual{elimination} rules.
48 \\
49 We can define a \pos{data} type with
50 & We can define a \dual{codata} type with
51 \\
52 \begin{lstlisting}
53 data <pos>Either</pos> a b
54 = Left a
55 + Right b
56 \end{lstlisting}
57 &
58 \begin{lstlisting}
59 codata <dual>Both</dual> a b
60 = first a
61 * second b
62 \end{lstlisting}
63 \\
64 This definition will effectively introduce two functions:
65 & This definition will effectively introduce two functions:
66 \\
67 \begin{lstlisting}
68 Left : a -> <pos>Either</pos> a b
69 Right : b -> <pos>Either</pos> a b
70 \end{lstlisting}
71 &
72 \begin{lstlisting}
73 first : <dual>Both</dual> a b -> a
74 second : <dual>Both</dual> a b -> b
75 \end{lstlisting}
76 \\
77 In order to \pos{destruct data} we have to use
78 \pos{patterns} which let you match on \pos{constructors}.
79 & In order to \dual{construct codata} we have to use
80 \dual{copatterns} which let you match on \dual{destructors}.
81 \\
82 \begin{lstlisting}
83 case e of
84 Left x -> e1
85 Right y -> e2
86 \end{lstlisting}
87 &
88 \begin{lstlisting}
89 merge x from
90 first x <- e1
91 second x <- e2
92 \end{lstlisting}
93 \\
94 Here, \postt{e} represents the \pos{value being
95 destructed}, and each branch represents a \pos{constructor
96 with which it might have been constructed}. We are effectively
97 \pos{dispatching on possible pasts} of
98 \postt{e}.
99 & Here, \dual{\texttt{x}} represents the \dual{value being
100 constructed}, and each branch represents a \dual{destructor
101 with which it might eventually be destructed}. We are effectively
102 \dual{dispatching on possible futures} of
103 \dualtt{x}.
104 \\
105 \end{both}
106
107 \section{Codata, Records, and Copatterns}
108 \raggedright
109
110 In the same way that named sums are a natural way to represent data,
111 records are a natural way to represent codata. In lieu of the above
112 syntax, one often sees codata represented as something more like
113
114 \begin{lstlisting}
115 record <dual>Both</dual> a b = <dual>{</dual> .first : a * .second : b <dual>}</dual>
116
117 x : <dual>Both</dual> Int Bool
118 x = <dual>{</dual> .first = 2 * .second = true <dual>}</dual>
119
120 assert x.first == 2
121 assert x.second == true
122 \end{lstlisting}
123
124 The \dualtt{merge} syntax is used here for conceptual
125 symmetry with \postt{case}. Additionally, the use of
126 copatterns is nicely dual with the extra expressivity that
127 patterns offer. For example, we can use nested patterns with
128 constructors of various types, as in this function which
129 processes a list of \postt{Either Int Bool} values
130 by summing the integers in the list until it reaches a
131 \postt{false} value or the end of the list:
132
133 \begin{lstlisting}
134 f : List (Either Int Bool) -> Int
135 f lst = case lst of
136 Cons (Left i) xs -> i <+> f xs
137 Cons (Right b) xs -> if b then f xs else 0
138 Nil -> 0
139 \end{lstlisting}
140
141 Similarly, we can define an infinite stream of pairs using
142 nested copatterns as so:
143
144 \begin{lstlisting}
145 s : Int -> Stream (Both Int Bool)
146 s n = merge x from
147 first (head x) <- n
148 second (head x) <- n > 0
149 tail x <- x
150 \end{lstlisting}
151
152 Copatterns are also practically expressive, as in this concise
153 and readable definition of the fibonacci numbers in terms of
154 the \dualtt{merge} expression:
155
156 \begin{lstlisting}
157 codata Stream a
158 = head a
159 * tail (Stream a)
160
161 zipWith : (a -> b -> c) -> Stream a -> Stream b -> Stream c
162 zipWith f s1 s2 = merge x from
163 head x <- f (head s1) (head s2)
164 tail x <- zipWith f (tail s1) (tail s2)
165
166 fibs : Stream Int
167 fibs = merge x from
168 head x <- 0
169 head (tail x) <- 1
170 tail (tail x) <- zipWith (<+>) x (tail x)
171 \end{lstlisting}
172
173 \section{Row-Typed \dual{Co}data}
174
175 \begin{both}
176 It is possible to build an \pos{open sum} by a small
177 modification of the datatype mechanism. Instead of
178 naming types and listing their associated
179 \pos{constructors}, we represent a type as a
180 \pos{list of constructors and types}.
181 &
182 It is possible to build an \dual{open product} by a small
183 modification of the datatype mechanism. Instead of
184 naming types and listing their associated
185 \dual{destructors}, we represent a type as a
186 \dual{list of destructors and types}.
187 \\
188 {\color{BrickRed}
189 \begin{verbatim}
190 {- .. -} : [ Left: a + Right: b ]
191 \end{verbatim}
192 }
193 &
194 {\color{NavyBlue}
195 \begin{verbatim}
196 {- .. -} : { first: a * second: b }
197 \end{verbatim}
198 }
199 \\
200 Use of a \pos{constructor} no longer specifies a
201 specific type, but rather \pos{any type that can be
202 constructed with that constructor}.
203 &
204 Use of a \dual{destructor} no longer specifies a
205 specific type, but rather \dual{any type that can be
206 destructed with that destructor}.
207 \\
208 \begin{lstlisting}
209 Left : a -> <pos>[</pos> Left: a + ... <pos>]</pos>
210 Right : b -> <pos>[</pos> Right: b + ... <pos>]</pos>
211 \end{lstlisting}
212 &
213 \begin{lstlisting}
214 first : <dual>{</dual> first: a * ... <dual>}</dual> -> a
215 second : <dual>{</dual> second: b * ... <dual>}</dual> -> b
216 \end{lstlisting}
217 \\
218 If we want to \pos{destruct} an open value like this, we
219 can use a \postt{case} construct, as before.
220 &
221 If we want to \dual{construct} an open value like this, we
222 can use a \dualtt{merge} construct, as before.
223 \\
224 {\color{BrickRed}
225 \begin{verbatim}
226 f : [ Left Int + Right Bool ] -> Int
227 f e = case e of
228 Left i -> i
229 Right b -> if b then 1 else 0
230 \end{verbatim}
231 }
232 &
233 {\color{NavyBlue}
234 \begin{verbatim}
235 f : Int -> { First Int * Second Bool }
236 f i = merge x from
237 first x <- i
238 second x <- i == 0
239 \end{verbatim}
240 }
241 \end{both}
242 \end{document}
1 # Subject-Oriented Programming
2
3 The idea of subject-oriented ostensibly originated as a critique of
4 object-oriented programming. In most statically-typed class-based
5 OOP systems[^2] beginners are encouraged to lay out their inheritance
6 hierarchy by basing it
7 on an ontology of concepts, i.e. the classic `Cat`-inherits-from-`Animal`
8 model. In practice, as people become more and more fluent with writing code
9 in an OOP language, they tend towards using interfaces more and more and
10 inheritance less and less, so as to couple the components less tightly.
11
12 [^2]: Are there statically-typed prototype-based OOP systems?
13
14 As Borges notes in "The Analytical Language of John Wilkins", "...it is clear
15 that there is no classification of the Universe not being arbitrary and full
16 of conjectures. The reason for this is very simple: we do not know what thing
17 the universe is." Necessarily, then, every ontology a programmer creates is
18 going to be arbitrary. A simple example might be edible plants: one programmer
19 will choose to structure their inheritance hierarchy such that `Tomato`
20 inherits from `Fruit`, as it derives from the flowering tissues of plants,
21 whereas another will declare that `Tomato extends Vegetable` because they are
22 not particularly sweet when eaten—and anyway, the codebase already has
23 `Salad makeSalad(ArrayList<Vegetable> ingredients)`.
24
25 Consequently, one problem with the inheritance-hierarchy-as-ontology system is (as Ossher
26 and Harrison describe in their paper) that it only works if your program is
27 guaranteed to always need/use that _particular_ ontology. To use a
28 somewhat more practical video game
29 example, let's say that you have game units in some manner of strategy game,
30 and you decide to structure their class hierarchy as follows:
31
32 - Unit
33 - Mobile
34 - Ally
35 - Enemy
36 - Stationary
37
38 This is fine... until you'd like to have a stationary enemy (e.g. some kind
39 of gun emplacement.) You could rearrange to the following:
40
41 - Unit
42 - Ally
43 - MobileAlly
44 - StationaryAlly
45 - Enemy
46 - MobileEnemy
47 - StationaryEnemy
48
49 ...but aside from the work of refactoring, this now requires a third subclass
50 of `Entity` to account for the neutral entities (like trees), and you can no
51 longer write functions that take _any_ `Mobile` unit. As indicated before,
52 seasoned OOP programmers might instead reach for interfaces (`implements
53 Mobile, Ally`), or you might avoid encoding this information in the type at
54 all (e.g. give each `Unit` instance variables corresponding to motility,
55 alignment, &c.)
56
57 Nevertheless, there are some advantages to static type hierarchies. Perhaps we would
58 like a hierarchy of mobility:
59
60 - Unit
61 - Immobile
62 - Mobile
63 - Walking
64 - Flying
65 - Swimming
66
67 At times, we would like to write methods in terms of anything that can move,
68 whereas at other times, we'd like to write methods solely in terms of swimming
69 units, and at other times, we'd like to write methods in terms of any unit at
70 all. This is one thing that Harrison and Ossher were attempting to preserve
71 when they described Subject-Oriented Programming, and is the salient way in which
72 it differs from Object-Oriented Programming: in an OOP system, an object `o`
73 _is_ an instantiation of a class `C`, whereas in a SOP system, an entity[^1] `e`
74 _can be seen as_ an activation of a subject `S`.
75
76 [^1]: Harrison and Ossher actually use the word _oid_, as short for _object
77 identifier_, but I prefer the term _entity_.
78
79 In a Subject-Oriented model, we could have several distinct class
80 hierarchies, e.g.
81
82 - Unit - Unit - Unit
83 - Immobile - Allied - NoAttack
84 - Mobile - PlayerOwned - Attacking
85 - Walking - FriendlyNPC - MeleeAttacking
86 - Flying - Enemy - AreaAttacking
87 - Swimming - Wild - RangeAttacking
88 - EnemyNPC
89
90 So if the player has an archer, it can be seen as a `Walking` unit, a
91 `PlayerOwned` unit, and a `RangeAttacking` unit. If we then write a method
92 that works with a `Mobile` unit, or an `Allied` unit, or an `Attacking`
93 unit, we can pass it the player's archer, despite the fact that these are
94 all distinct ontologies. I'm handwaving wildly over the implementation
95 details for the moment, and to some extent I'm diverging from the original
96 conception, but I'm trying to stay roughly faithful to the ideas as
97 presented.
98
99 Curiously, Subject-Oriented Programming has a large conceptual overlap with
100 an idea quite in vogue among game developers, that of Component-Entity
101 Systems. What Harrison and Ossher would call _subjects_ are there called
102 _components_, and many of the details as implemented are different, but the
103 critical idea—that of an `entity` which takes on various roles as needed—is
104 present in both systems.
105
106 # A Subject-Oriented λ-Calculus
107
108 What would a subject-oriented λ-calculus look like? I'm gonna come up with
109 something that _looks_ like subject-oriented programming if you squint, but
110 it's not going to be particularly faithful to the original formulation.
111
112 What operations are valid on a particular entity? Well, it depends on the
113 subjects that entity has associated. We have a set of them, and each one
114 has fields and methods associated. That suggests that perhaps an entity
115 should look sort of like a _record_ of sorts—in particular, a record in a
116 language with _row polymorphism_. I'm gonna come up with a code sample, and
117 then elaborate on which primitive operations we need:
118
119 subject S of Int
120 subject T of Bool
121
122 f : {S,T,...} -> Int
123 f e = get S i from e
124 T b from e
125 in if b then i else 0
126
127 y : Int
128 y = let x = ε
129 x' = add S 3 to x
130 x'' = add T True to x'
131 in f x''
132
133 What's going on here? Well, we're declaring our _subjects_ up front, but
134 those are effectively labels for a record. We use those with the
135 `add ... to ...` operation, which acts like record update, and extract
136 them with `get ... from ... in ...`. I'm here using epsilon as the
137 _"empty" entity_ but that's more or less arbitrary.
138
139
140
141 XXX -- delete?
142
143 So let's start with the expression language. We'll start with a rough
144 simply-typed λ-calculus with products, sums, and a unit type, and then
145 add things one-by-one.
146
147 e := (e, e) | inl e | inr e | unit | λx:t. e
148
149 So far, so good. The next thing we can do is create an _empty entity_,
150 which is an entity with no subjects associated with it.
151
152 e := ... | ε
153
154 we can also _add a subject_ to an entity
155
156 e := ... | add S e to e
157
158 and _access a subject's field_
159
160 e := get S x from e in e
161
162 Instead of explaining what these operations do, I'll jump straight to a
163 chunk of code:
164
165 To begin with, we declare two different subjects, `S` (which requires an
166 argument of type `Int`) and `T` (which requires an argument of type
167 `Bool`). We then define a function `f`, which takes an entity, and
168 extracts from it the subject `S` with its associated value and the subject
169 `T` with its associated value, and then uses those values in rather
170 traditional functional code.
171
172 The definition of the value `y` is done by creating an intermediate
173 entity called `x`, then creating a new entity `x'` that is the same as
174 `x` but nevertheless able to be viewed as an `S` as well, and then
175 an entity `x''` which can be viewed as either an `S` or a `T`, and then
176 passes that to `f` (which requires an entity that can be seen as
177 both `S` and `T` simultaneously.) The final value of `y` is going to
178 be `3`.
179
180 This gives us a clue about how types will work in our calculus, as
181 well—an entity must have a type corresponding to the set of subjects it
182 implements. Consequently, we have the following types in the program:
183
184 x : {}
185 x' : {S}
186 x'' : {S,T}
187 y : Int
188 f : {S,T} -> Int
189
190 This is of course a ridiculous toy program, but we can begin to see some
191 of the previous features of Subject-Oriented Programming peeking out at
192 us:
193
194 subject HasPosition of Int * Int
195 subject HasVelocity of Int * Int
196
197 updateEntity : Ref {HasPosition,HasVelocity} -> ()
198 updateEntity someRef =
199 let e = !someRef in
200 get HasPosition (x,y) from e
201 HasVelocity (dx,dy) from e in
202 someRef := add HasPosition (x + dx, y + dy) to e
203
204 And so forth. A practical
205
206
207 # The Mezzo Language
208
209 There's a functional language—an ML variant—being developed by Jonathan
210 Protzenko and François Pottier at INRIA. It's an interesting effort. You
211 can read about its motivations and capabilities elsewhere, but the most
212 interesting is the way it handles what it calls _permissions_. A _permission_
213 corresponds somewhat to what other languages call a _type_, but the semantics
214 are... different.
215
216 For example, in an ML-like language, I can write a function which takes a pair
217 of values of the same type and swaps them.
218
219 fun swap r =
220 let val (x, y) = !r in
221 r := (y, x)
222 end
223
224 This SML function has its type inferred as `('a * 'a) ref -> unit`, and after
225 it executes, whatever pair you've passed it will have had its elements
226 swapped. However, in SML, there is no way of doing this with pairs of type
227 `('a * 'b) ref`, because you can't _change_ the type of something that exists.
228 You would have to construct a new pair with a different type.
229
230 Mezzo is different. In Mezzo, you don't have types, you have _permissions_,
231 which you can understand as the permission to access a particular part of the
232 heap as a particular kind of data. As such, you are allowed to write a function
233
234 val mswap: [a, b] (consumes x: mpair a b) -> (| x @ mpair b a)
235
236 This type is interpreted as _consuming_ the permission to access `x` as a mutable
237 pair of types `a` and `b`, and _producing_ the permission to access `x` as a mutable
238 pair of types `b` and `a`. To be concise, it swaps the elements of the mutable pair
239 _without allocating a new pair_ and _in a type-safe way_[^3]. There's a fair amount
240 of extra complexity I'm omitting, and I encourage the reader to read the relevant
241 blog posts, because there are a lot of interesting things going on there.
242
243 [^3]: Or a permission-safe way, anyway.
244
1 Haskell records have lots of problems. Here's one that came up for me today.
2
3 You are allowed to export record members without exporting the constructor,
4 for example, if you want to ensure some property is true of the constructed
5 values. In the following example, the field `isNeg` is effectively a function
6 of the field `num`:
7
8 module Foo(mkRec, num, isNeg) where
9
10 data Rec = Rec
11 { num :: Int
12 , isNeg :: Bool
13 }
14
15 mkRec :: Int -> Rec
16 mkRec n = Rec n (n < 0)
17
18 Another module can't use the `Rec` constructor, but can observe the values
19 using the exported accessors
20
21 module Bar where
22
23 addRecs :: Rec -> Rec -> Rec
24 addRecs r1 r2 = mkRec (num r1 + num r2)
25
26 Unfortunately, there's a hole here, which is that exporing the accessors
27 allows us to use record update syntax, which means that we can now construct
28 arbitrary values:
29
30 constructAnyRec :: Int -> Bool -> Rec
31 constructAnyRec n b = mkRec 0 { num = n, isNeg = b }
32
33 There is a way around this, namely, by rewriting the original module with
34 manual accessors for `num` and `isNeg`:
35
36 module Foo2(mkRec, num, isNeg) where
37
38 data Rec = Rec
39 { _num :: Int
40 , _isNeg :: Bool
41 }
42
43 num :: Rec -> Int
44 num = _num
45
46 isNeg :: Rec -> Bool
47 isNeg = _isNeg
48
49 mkRec :: Int -> Rec
50 mkRec n = Rec n (n < 0)
51
52 However, I'd assert that, morally, the _correct_ thing to do would be to
53 disallow record update at all if the constructor is not in-scope. The
54 purpose of hiding the constructor at all is to ensure that a programmer
55 must perform certain computations in order to construct a valid value,
56 e.g. to enforce invariants on constructed data (as I'm doing here), or
57 to avoid the possibility of pattern-matching on data. If you a programmer
58 hides a constructor but exports its accessors, then generally I'd assert
59 it's because of the former reason, so it would be sensible to prevent
60 record update, as you could always write your own updates, if you so
61 desire.
62
63 Of course, pointing out this flaw in light of the other problems with the
64 Haskell record system is like complaining about the in-flight movie on
65 a crashing plane, but still.
1 # Cherenkov: Basics
2
3 A Cherenkov program evalutes to a JSON value, and in fact should include
4 a standalone program which can be used to evaluate a Cherenkov program to
5 its JSON equivalent. Cherenkov also includes a little bit of YAML-like
6 sugar but does not include the full generality of YAML. (For example,
7 references are not valid Cherenkov: it has something better!)
8
9 A simple configuration file might look like
10
11 ~~~~
12 # the first host
13 - hostname: "foo"
14 forward-agent: true
15 user: "u1"
16 # the second host
17 - hostname: "bar"
18 local-command: "/bin/setup-shell -q"
19 user: "u2"
20 ~~~~
21
22 This evaluates to the JSON document
23
24 ~~~~
25 [ { "hostname": "foo"
26 , "forward-agent": true
27 , "user": "u1"
28 }
29 , { "hostname": "bar"
30 , "local-command": "/bin/setup-shell -q"
31 , "user": "u2"
32 }
33 ]
34 ~~~~
35
36 Note that the comments are removed in the final output, as comments are
37 not valid JSON.
38
39 # Cherenkov: Expressions
40
41 Expressions can be introduced using the single-quote character. These
42 will be evaluated and their result spliced into the final output. If
43 the expression is a string, a number, or a bool, it will result in
44 a value of the corresponding JSON type:
45
46 ~~~~
47 - 1
48 - '(0.5 + 1.5)
49 - '("foo" ++ "bar")
50 ~~~~
51
52 This evaluates to
53
54 ~~~~
55 [1, 2.0, "foobar"]
56 ~~~~
57
58 If the result is a constructor, then it will be represented as an
59 object with a single key that corresponds to the name of the
60 constructor:
61
62 ~~~~
63 - '(Left 5)
64 ~~~~
65
66 evaluates to
67
68 ~~~~
69 [{"Left": 5}]
70 ~~~~
71
72 If the result is a record, then it will be represented as a JSON
73 object:
74
75 ~~~~
76 - '{x: 2, y: 3}
77 ~~~~
78
79 evalutes to
80
81 ~~~~
82 [{"x": 2, "y": 3}]
83 ~~~~
84
85 The expression language includes several functional features, such as
86 let-bindings, anonymous functions with a Haskell-like syntax, and
87 function calls:
88
89 ~~~~
90 foo: '(let lst = [1,2,3] in map (\x.x+1) lst)`
91 ~~~~
92
93 evaluates to
94
95 ~~~~
96 {"foo": [2,3,4]}
97 ~~~~
98
99 # Cherenkov: Declarations
100
101 Global declarations can be written by prefacing a declaration with
102 the keyword `with`. These declarations do not appear in the output:
103
104 ~~~~
105 with incrAll lst = map (\x.x+1) lst
106
107 '(incrAll [1,2,3])
108 ~~~~
109
110 evaluates to
111
112 ~~~~
113 [2,3,4]
114 ~~~~
115
116 Functions can be defined by cases, and can have type signatures as
117 well. The cases are given in an indented block:
118
119 ~~~~
120 with intersperse : List String -> String -> String
121 (x::xs) i = x ++ i ++ intersperse xs i
122 [] i = ""
123 ~~~~
124
125 Values and types can also be defined in `with`-blocks:
126
127 ~~~~
128 with type Server =
129 { hostname String
130 , username String
131 , portNum Int
132 }
133
134 with defaultServer : Server =
135 { hostname: "foo.bar"
136 , username: "guest"
137 , portNum: 9999
138 }
139
140 servers:
141 - 'defaultServer
142 - '(defaultServer { hostname: "blah" })
143 - '(defaultServer { port: 9876 })
144 ~~~~
145
146 evaluates to
147
148 ~~~~
149 {"servers":
150 [ { "hostname": "foo.bar"
151 , "username": "guest"
152 , "postNum": 9999
153 }
154 , { "hostname": "blah"
155 , "username": "guest"
156 , "postNum": 9999
157 }
158 , { "hostname": "foo.bar"
159 , "username": "guest"
160 , "postNum": 9876
161 }
162 ]
163 ~~~~
164
165 # Cherenkov: Type System
166
167 If you've used Haskell, OCaml, or SML, the type system in Cherenkov
168 should look vaguely similar. We have records and sums, the former
169 denoted by curly braces and the latter by square brackets:
170
171 ~~~~
172 # defining a sum type
173 with type MyVal =
174 [ MyInt Int
175 , MyBool Bool
176 ]
177
178 # using a sum type
179 with f : MyVal -> Int
180 MyInt n = n
181 MyBool b = if | b -> 1
182 | else -> 0
183
184 # defining a product type
185 with type MyRec =
186 { myString String
187 , myFloat Float
188 }
189
190 # using a product type
191 with g : MyRec -> String
192 r = r.myString ++ " and " ++ (if | r.myFloat > 0 -> "positive"
193 | else -> "negative")
194 ~~~~
195
196 You can also define polymorphic types:
197
198 ~~~~
199 # defining a polymorphic sum type
200 with type Either a b = [ Left a, Right b ]
201
202 # defining a polymorphic product type
203 with type Both a b = { first a, second b }
204 ~~~~
205
206 Cherenkov has a much looser type system than some other strongly-typed
207 langauges. It technically has no type declarations---it has only type
208 aliases. You can use as many or as few type declarations as you'd like,
209 as types are inferred if you don't use them. I can use the above functions
210
211 ~~~~
212 # using a sum type without type annotations
213 with f
214 MyInt n = n
215 MyBool b = if | b -> 1
216 | else -> 0
217
218 # using a product type
219 with g r =
220 r.myString ++ " and " ++ (if | r.myFloat > 0 -> "positive"
221 | else -> "negative")
222 ~~~~
223
224 And they will have the inferred types
225
226 ~~~~
227 f : [ MyInt Int, MyBool Bool ] -> Int
228 g : { myString String, myFloat Float, rest } -> String
229 ~~~~
230
231 That second type deserves a little bit more attention. Cherenkov has
232 _row polymorphism_, which means that the second version of `g` will
233 accept as argument not just a record with `myString` and `myFloat`
234 fields, but a record with any number of additional fields, which is
235 what the `rest` in the type represents. This can also appear in
236 the return position: for example, a function like
237
238 ~~~~
239 with f r = r { z: r.x + r.y }
240 ~~~~
241
242 has an inferred type
243
244 ~~~~
245 f : { x: Int, y: Int, rest } -> { x: Int, y: Int, z: Int, rest }
246 ~~~~
247
248 as the rest of the fields are passed through unchanged. The `rest`
249 variable also occurs when we underspecify a constructor: consider
250 the function `g`
251
252 ~~~~
253 with g
254 Left x = Left (x == 0)
255 rs = rs
256 ~~~~
257
258 which has the inferred type
259
260 ~~~~
261 g : [ Left: Int, rest ] -> [ Left: Bool, rest ]
262 ~~~~
263
264 i.e. this can be called on any constructor and it will pass it through
265 unchanged. If this is too lax for us—if we'd prefer to limit the
266 constructors to a particular set—then we can list them explicitly:
267
268 ~~~~
269 with g'
270 Left x = Left (x == 0)
271 Right y = Right (y : Bool)
272 ~~~~
273
274 which has the inferred type
275
276 ~~~~
277 g' : [ Left: Int, Right: Bool ] -> [ Left: Bool, Right: Bool ]
278 ~~~~
279
280 Because all types are aliases, it means that recursive types and
281 polymorphic types are also aliases for explicit type fixpoints and
282 quantification, so the type alias
283
284 ~~~~
285 type MyList a =
286 [ MyCons (a, MyList a)
287 , MyNil
288 ]
289 ~~~~
290
291 will desugar to the type `\a. b is [ MyCons (a, b), MyNil ]`, where
292 `\ var . typ` represents quantification as `var is typ` represents
293 a fixpoint. It is possible but ill-advised to use these in actual
294 config files—in practice, these exist becuase they provide a nice
295 underlying theory to a terse and implicit system that preserves
296 type-safety while requiring a minimum of explicit direction from
297 the programmer.
298
299 # Cherenkov: Totality
300
301 Cherenkov is strongly-typed and total. This means that certain invalid
302 operations are not allowed in Cherenkov programs, and that Cherenkov
303 programs will never loop forever. Certain restrictions are therefore
304 placed on Cherenkov programs:
305
306 - Cherenkov values may not be recursive, and therefore must be
307 finite in size
308 - Cherenkov functions may only be recursive _if they recurse on some
309 subpart of their input_, or (as a special case) if they recurse
310 with some numeric parameter that trivially moves towards a terminal
311 condition.
312 - Cherenkov sum types may be be recursive, but Cherenkov product
313 types _must not_ be. (The can be underneath another sum, so e.g.
314 a type like `rec is { a Int, b rec }` is disallowed as it would
315 involve an infinite value, whereas
316 `rec is { a Int, b [ Nothing, Just rec ] }` is fine. This only
317 works if the sum type in question has a non-recursive constructor:
318 we cannot use the type
319 `rec is { a Int, b [ Left rec, Right rec ] }`.
320
321 For example, in the following code, `foo` is well-typed and will
322 also pass the totality checker, and `bar` is well-typed but would
323 be rejected by the totality checker as it is not trivially
324 recursing towards a terminal condition:
325
326 ~~~~
327 with foo : List Int -> Int
328 (x::xs) = x + foo xs
329 [] = 0
330
331 with bar : List Int -> Int
332 (x::xs) = bar (x+1::xs)
333 [] = bar []
334 ~~~~
335
336 This means that some functions which may be well-founded are not
337 expressible, because it is not trivial to see that they terminate:
338
339 ~~~~
340 with collatz : Int -> Bool
341 n = if | n == 1 -> true
342 | n < 1 -> false
343 | mod n 2 == 0 -> collatz (div n 2)
344 | else -> collatz (n * 3 + 1)
345 ~~~~
346
347 All sum and product types are row-polymorphic, and therefore do not
348 need to be declared, as those types can be inferred from use. You
349 can, however, declare aliases for those, if you'd like (especially
350 as recursive types can get quite complicated without aliases!)
1 # Data
2
3 In most statically-typed functional languages like ML or Haskell, the primary
4 mechanism you have for data types is _named sums_. There are two important
5 aspects of data types in general: the way you construct them, and the way
6 you destruct them. Let's start with constructing them. (I'm going to use a
7 pseudocode in this post that most closely resembles Idris, but is not
8 supposed to be any particular language.)
9
10 To define a type `T`, one will list the possible constructors and their
11 arguments:
12
13 data T = I Int | B Bool
14
15 This example gives you two possible constructors, `MyInt` and `MyBool`,
16 which you can think of as defining a type and two functions:
17
18 T : Type
19 I : Int -> T
20 B : Bool -> T
21
22 In addition to those, you also get the ability to destruct values created
23 with those constructors, which is done using the `case` construct.
24 `case` allows you to determine which constructor was used
25 to create a particular datum of type `MyType`:
26
27 f : T -> Int
28 f e =
29 case e of
30 I i -> i
31 B b -> if b then 1 else 0
32
33 # Codata
34
35 So: data is defined by its constructors. What's the opposite of data?
36 Why, codata, of course! And if data is defined using constructors, then
37 codata is defined using destructors.
38
39 What does this mean? Consider the following codata declaration in a hypothetical
40 version of Haskell:
41
42 codata T' = I' Int & B' Bool
43
44 This gives you two possible _destructors_, `I'` and `B'`, which means
45 you can think of them as two functions
46
47 I' : T' -> Int
48 B' : T' -> Bool
49
50 They're not constructors, they're _accessor_ functions. How do we create a
51 value, then? Using the dual of `case`, which I will call `merge`:
52
53 f' : Int -> T'
54 f' e =
55 merge
56 I <- e
57 B <- if e == 1 then True else False
58
59 How do we interpret this snippet of code? This creates a function `f'` which
60 takes an integer and returns a value of type `MyType'` (the opposite of what
61 `f` does using `case`.) The `merge` construct lists each possible destructor,
62 and then the value that will be returned when that destructor is called on it.
63
64 Now, you might have noticed that this isn't exactly dual. When we use a
65 `case` construct, we are _dispatching on the history of a value_, which
66 means we can _pattern-match over the constructor_. When we use merge as above,
67 we're just listing
68
69 f' :: Int -> MyType'
70 f' e =
71 merge' e with
72 MyInt <- (λi.i)
73 MyBool <- (λi. if i == 1 then True else False)
74
75 Let's look at a few trivial examples to see how these two ways of defining
76 data are dual. If we have a data definition with a single constructor
77
78 data X = X Int
79
80 then it follows that
81
82 i == case' (X i) of X -> (λj. j)
83
84 i.e. if we construct and then destruct a value, then it's the same as having
85 done nothing. We can assert the corresponding inside-out equivalence for
86 codata:
87
88 codata X' = X' Int
89 i == X' (merge' i with X' <- (λj. j))
90
91 Where data naturally maps to sums, codata naturally maps to products. In
92 an ML-like language, we can easily define the basic sum type `a + b` in terms
93 of the named-sum mechanism
94
95 data a :+: b = InL a | InR b
96
97 but cannot do the same with products, as all constructors of a type `T` must
98 be of the form `a -> T` for some specific `a`—consequently, we cannot define
99 a product type using this named-sums mechanism unless we already have a
100 product type. (In Haskell, we can, because Haskell allows its constructors
101 to be curried, so a constructor of the form `a * b -> T` can be curried to
102 `a -> b -> T`, but let us restrict ourselves to single-argument constructors
103 for this discussion.)
104
105 In a language with this `codata` mechanism, we could easily define that
106 product type in terms of its two destructors:
107
108 codata a :*: b = Fst a & Snd b
109
110 In practice, it's often convenient to express codata as records and its
111 destructors as projection from those records. That means the `MyType'` example
112 above would look like
113
114 codata MyType' = { MyInt' :: Int, MyBool' :: Bool }
115
116 f' :: Int -> MyType'
117 f' e = { MyInt' = e, MyBool' = if e == 1 then True else False }
118
119 codata X' = { X' :: Int }
120 i == { X' = i }.i
121
122 but while the syntax here is different, the semantics remain identical.
123
124 # Row Polymorphism
125
126 Note that each destructor here unambiguously identifies the type of the
127 codata is projects from—if we use `MyInt'`, then we are necessarily
128 looking at projecting from a value of type `MyType'`. This is very much
129 how Haskell records work, but people generally would prefer something
130 more parametric for records. One system for doing this is called _row
131 polymorphism_.
132
133 In a row-polymorphic record system, your accessors don't name a specific
134 type, but rather, _some record type for which that accessor holds_. That
135 means an accessor like `MyInt'` would have a type like
136
137 MyInt' :: (MyInt' Int <~ r) => r -> Int
138
139 This means that we can write functions which are agnostic as to the rest
140 of the fields in the records, so long as they contain the fields we want:
141
142 h :: (MyInt' Int <~ r, MyBool' Bool <~ r) => r -> Int
143 h e = if e.MyBool' then e.MyInt' else 0
144
145 In the above function, we could supply a record like
146 `{ A = "foo", B = 7.7, MyBool' = True, MyInt' = 2 }` to `h` and everything
147 would be fine. Notice that we've dispensed entirely with the type `MyType'`.
148 This could, hypothetically, cause some usability problems with respect to
149 type inference: before, if had omitted the type declaration and misspelled
150 `MyInt'` as `MyItn'`, then our program would have immediately failed
151 to compile because no such accessor existed, but now, it's possible
152 for `h` to compile successfully and infer the incorrect type
153
154 h :: (MyItn' Int <~ r, MyBool' Bool <~ r) => r -> Int
155
156 and either fail to compile because of a failure of inference elsewhere or
157 succeed with unintended semantics (e.g. if `g` is an exposed library
158 function that is not used elsewhere in the module.)
159
160 Additionally, row-polymorphic records will incur a higher runtime cost
161 then our previous treatment. If a record is unambiguously identified by
162 a set of destructors, then we can compile those records as a fixed-size
163 chunk of memory with known offsets for each destructor. This is not the
164 case with row-polymorphic records.
165
166 Now we've developed codata as the dual of data and identified the record-like
167 representation of codata. What's the dual of row-polymorphic records?
168
169 # Dualizing Row Polymorphism
170
171 For convenience sake, let's look at what row polymorphism looks like
172 with our previous syntax. Each accessor `A`, when used to extract a value of
173 type `t`, has the type
174
175 A :: (A t <~ r) => r -> t
176
177 and we construct row-polymorphic codata with a set of destructors and our
178 `merge` construct, which infers the type produced by the set of destructors
179 it is given:
180
181 g :: Int -> { A :: Int & B :: Bool }
182 g e =
183 merge
184 A <- e
185 B <- if e == 1 then True else False
186
187 That means that the dual of row polymorphism would have polymorphic
188 constructors, so that when a constructor `C`, when used with a value of
189 type `t`, has the type
190
191 C :: (C t ~> s) => r -> s
192
193 and our `case` construct infers the type _destructed_ by the constructors
194 it matches over:
195
196 g' :: [ A :: Int | B :: Bool ] -> Int
197 g' e =
198 case e of
199 A i -> i
200 B b -> if b then 1 else 0
201
202 As it turns out, this already exists in OCaml, where data types of this
203 sort are called _polymorphic variants_. The problems I mentioned above
204 with row-polymorphic records apply here, as well: if I match over `MyItn`
205 instead of `MyInt`, then that function might compile and its type will
206 fail to unify elsewhere, or it might be fine. This is particularly
207 insidious with catch-all patterns: a programmer who wrote this and
208 missed the type might believe that x is True when it in fact is
209 False:
210
211 isInt e =
212 case e of
213 MyItn _ -> True
214 _ -> False
215
216 x = isInt (MyInt 5)
217
218 This could be mitigated by forbidding catch-all patterns in a case. The
219 following code would not even type-check:
220
221 isInt e =
222 case e of
223 MyItn _ -> True
224 MyBool _ -> False
225
226 x = isInt (MyInt 5)
227
228 Like row-polymorphic records, these sums are also harder to represent at
229 runtime than traditional sums.
230
231 # Are They Useful?
232
233 Yes. In their own way.
1 # So, What's All This, Then?
2
3 So, you know what a wiki is, right? It's one of those, with a few
4 little twists. I'll walk through by doing:
5
6 # Just A Wiki
7
8 The data model of a wiki is basically that you have some kind of
9 rich text structure—so you have some formatting primitives, like
10 bolding or lists or whatnot—as well as the notion of _links_,
11 which are references to other pages which you can follow. Each
12 link references a page—or, sometimes, a point within a page—but
13 each page is basically that.
14
15 Commonplace has a bit more structure. It refers to pages as
16 _scraps_, for reasons we'll see in just a little bit. You
17 create a new scrap and use Commonplace's mildly idiosyncratic,
18 LaTeX-inspired markup language to write a page:
19
20 ~~~~
21 \strong{Welcome} to this new page! We can link to an outside
22 page like so: \link{http://gdritter.com|my home page}.
23 We can also link to \there{foo|a wiki page that doesn't exist}.
24 ~~~~
25
26 That link will render a different color to indicate that it's a
27 _broken_ link, as the page it references doesn't yet exist. If
28 we follow it, we'll have the opportunity to create that page.
29
30 ~~~~
31 This is now our \em{Foo} page.
32 ~~~~
33
34 Okay, now that we've done that, we can go back and notice that
35 the link color has changed: we can click on it and it'll take
36 us to our newly created _foo_ page. There's a little twist on what
37 Commonplace allows us to do, though. Let's change the initial
38 page a little bit:
39
40 ~~~~
41 \strong{Welcome} to this new page! We can also link to
42 \link{foo|another page}. However, we can also do this:
43
44 \here{foo}
45 ~~~~
46
47 If we look at the page now: look! The entirety of the _foo_ scrap
48 is now _embedded_ within the larger scrap. We don't just link, we
49 also _compose_ scraps into larger pages. We can do this to a greater
50 depth, too:
51
52 [image here]
53
54 although there is a cut-off depth. Commonplace is also aware of
55 recursive pages, and will be intelligent about representing them:
56
57 [image here]
58
59 # Transparent Editing
60
61 Now let's try something a little different:
62
63 ~~~~
64 This is an outer page.
65
66 \page {
67 This is an inner page.
68 }
69 ~~~~
70
71 Now when we render this, we get this:
72
73 [image here]
74
75 Notice how we wrote a single page, but when we view it, it appears
76 to be a scrap embedded within another! What has happened is Commonplace
77 understands the `\page { ... }` construct to mean, "Create a new scrap
78 and embed it directly here." This means that we can still have a finer,
79 more granular structure but without you having to edit all the scraps
80 individually. The editing view gives you the ability to select a deeper
81 view (which lets you edit embedded scraps inline) or a shallower view
82 (which lets you see the `here {...}` references.)
83
84 This could be useful in paper-editing: you can write your paper with each
85 subsection in its own `\page { ... }` block, but later can rearrange
86 those subsections by switching to a higher-level editing view and then
87 rearranging the `\here { ... }` references.
88
89 # Deeper Structure
90
91 All the above pages have been unstructured text, but Commonplace also
92 understands _structured_ scraps. We've actually already seen one, but
93 haven't realized it: the `\link { ... }` element. That appeared to be
94 a markup element, but behind-the-scenes, links work like embedded
95 `\page { ... }` elements, storing their contents in a separate
96 scrap. Notice that links have two parts: the actual URL and the
97 description text.
98
99 ~~~~
100 An external link to \link{http://www.example.com|an example site}.
101 ~~~~
102
103 This implies that we can, like scraps, edit a link _individually_,
104 outside of the context of the page in which it appears. And if we do:
105
106 [image here]
107
108 It has a separate _named_ field for each part of the link. If we
109 modify it here, we can see the changes appear back in the page where
110 we used that link:
111
112 [image here]
113
114 This can give us tremendous flexibility: if we use a link throughout
115 our wiki, but the site to which we're linking goes down, we can
116 transparently replace the link everywhere it appears with an
117 [Internet Archive version] of the same thing.
118
119 Links are only one kind of type, and one with relatively little
120 structure. A good example of a more deeply-structured type is a
121
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2 <html xmlns="http://www.w3.org/1999/xhtml">
3 <head>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <meta http-equiv="Content-Style-Type" content="text/css" />
6 <meta name="generator" content="pandoc" />
7 <title></title>
8 <style type="text/css">code{white-space: pre;}</style>
9 </head>
10 <body>
11 <p>I basically always use some program in the <code>daemontools</code> family on my computers. My home laptop and desktop are booted with an init system (<code>runit</code>) based on <code>daemontools</code>, while many of the systems I set up elsewhere boot a vanilla distribution but immediately set up a <code>daemontools</code> service directory as a secondary service management tool. Quite frankly, it's one of the best examples of good Unix design and at this point I wouldn't want to go without it.</p>
12 <p>This is a high-level introduction to the <em>idea</em> of <code>daemontools</code> rather than a full tutorial: to learn how to set it up in practice, <a href="http://cr.yp.to/daemontools.html">djb's own site</a> as well as <a href="http://rubyists.github.io/2011/05/02/runit-for-ruby-and-everything-else.html">a handful</a><sup><a href="#fn1" class="footnoteRef" id="fnref1">1</a></sup> <a href="http://www.troubleshooters.com/linux/djbdns/daemontools_intro.htm">of others</a> are better references.</p>
13 <h1 id="what-is-daemontools">What is Daemontools?</h1>
14 <p>The <em>core</em> of <code>daemontools</code> is just two programs: <code>svscan</code> and <code>supervise</code>. They're very straightforward: <code>svscan</code> takes a single optional argument, and <code>supervise</code> takes a single mandatory one.</p>
15 <p><a href="http://cr.yp.to/daemontools/svscan.html"><code>svscan</code></a> watches a directory (if none is specified, then it will watch the current working directory) and checks to see if new directories have been added. Any time a new directory is added, it starts an instance of <code>supervise</code> pointing at that new directory<sup><a href="#fn2" class="footnoteRef" id="fnref2">2</a></sup>.</p>
16 <p>And that's all that <code>svscan</code> does.</p>
17 <p><a href="http://cr.yp.to/daemontools/supervise.html"><code>supervise</code></a> switches to the supplied directory and runs a script there called <code>./run</code>. If <code>run</code> stops running for <em>any reason</em>, it will be started again (after a short pause, to avoid hammering the system.) It will also not start the <code>./run</code> script if a file called <code>./down</code> exists in the same directory. Extra data about the running process gets stored in a subdirectory called <code>supervise</code>, and a few other tools can be used to prod and modify that data—for example, to send certain signals to kill the running program, to temporarily stop it, or to see how long it has been running.</p>
18 <p>And that's almost all that <code>supervise</code> does.</p>
19 <p>One extra minor wrinkle is that if <code>supervise</code> is pointed at a directory that also contains a subdirectory called <code>./log</code>, and <code>./log/run</code> also exists, then it will monitor that executable <em>as well</em> and point the stdout of <code>./run</code> to the stdin of <code>./log/run</code>. This allows you to build a custom logging solution for your services if you'd like. The <code>./log</code> directory is optional.</p>
20 <p>So, how does this run a system? Well, you point <code>svscan</code> at a directory that contains a subdirectory for each service you want to run. Those services are generally small shell scripts that call the appropriate daemon in such a way that it will stay in the foreground. For example, a script to run <code>sshd</code> might look like:</p>
21 <pre><code>#!/bin/sh
22
23 # redirecting stderr to stdout
24 exec 2&gt;&amp;1
25
26 # the -D option keeps sshd in the foreground
27 # and the -e option writes log information to stderr
28 exec /usr/sbin/sshd -D -e</code></pre>
29 <p>And your directory structure might look like</p>
30 <pre><code>-+-service/
31 |-+-ngetty/
32 | |---run
33 | |-+-log/
34 | |---run
35 |-+-sshd/
36 | |---run
37 | |-+-log/
38 | |---run
39 |-+-crond/
40 | |---run
41 | |-+-log/
42 | |---run</code></pre>
43 <p>Once you point <code>svscan</code> at this, you end up having a process tree where <code>svscan</code> is managing multiple <code>service</code> instances which in turn manage their respective services and logging services:</p>
44 <pre><code>-svscan-+-service-+-ngetty
45 | `-log-service
46 +-service-+-sshd
47 | `-log-service
48 +-service-+-cond
49 | `-log-service</code></pre>
50 <p>This design has some pretty amazing practical advantages, many of which are attributable to the fact that <code>daemontools</code> <em>is written in terms of Unix idioms</em>. The &quot;Unix way&quot; gets a fair amount of derision—some well-deserved, some not—but <code>daemontools</code> is a good example of how embracing the idioms of your system can produce better, more flexible software. Consider the following problems and their <code>daemontools</code> solutions:</p>
51 <h2 id="testing-a-service-before-you-start-it">Testing a Service Before You Start It</h2>
52 <p>The <code>./run</code> script is a plain executable. If it runs and stays in the foreground, doing what it should do, it's correct. If it doesn't, then there's a problem. That's also the only code path, which is a sharp contrast to the infamously difficult-to-write <code>sysvinit</code> scripts, where <code>start</code> and <code>stop</code> and <code>status</code> and so forth must all be tested in various system states<sup><a href="#fn3" class="footnoteRef" id="fnref3">3</a></sup>.</p>
53 <h2 id="starting-and-stoping-a-service">Starting and Stoping a Service</h2>
54 <p>All you do is create or delete a service directory. The most common way of doing this is to create the service directory elsewhere, and then create a symlink into the service directory to start it. This lets you delete a symlink without deleting the main directory, and furthermore ensures that the 'creation' of the directory is atomic.</p>
55 <p>Another tool, <code>svc</code>, lets you send signals to the running processes (e.g. <code>svc -p</code> sends a <code>STOP</code> signal, and <code>svc -d</code> sends a <code>TERM</code> signal as well as telling <code>supervise</code> to hold off on restarting the service otherwise.)</p>
56 <h2 id="express-service-dependencies">Express Service Dependencies</h2>
57 <p>The <code>daemontools</code> design allows for various helper tools. One of them is <code>svok</code>, which finds out whether a given service is running. This is just another Unix program that will exit with either <code>0</code> if the process is running, or <code>100</code> if it is not. That means we can write</p>
58 <pre><code>#!/bin/sh
59 svok postgres || exit 1
60 exec 2&gt;&amp;1
61 exec python2 some-web-app.py</code></pre>
62 <p>and the script will die (prompting <code>svscan</code> to wait a moment and then restart it) unless <code>postgres</code> is already running.</p>
63 <h2 id="express-resource-limits">Express Resource Limits</h2>
64 <p><code>daemontools</code> has several other applications that can enforce various resource limits or permissions. These are not part of the service mechanism—instead, they simply modify <em>the current process</em> and then <code>exec</code> some other command. That means that you can easily incorporate them into a service script</p>
65 <pre><code>#!/bin/sh
66 exec 2&gt;&amp;1
67 # change to the user &#39;sample&#39;, and then limit the stack segment
68 # to 2048 bytes, the number of open file descriptors to 2, and
69 # the number of processes to 1:
70 exec setuidgid sample \
71 softlimit -n 2048 -o 2 -p 1 \
72 some-small-daemon -n</code></pre>
73 <p>These aren't actually special, and don't have anything to do with the <code>daemontools</code> service mechanism. Any shell script can incorporate <code>setuidgid</code> or <code>softlimit</code>, even if those scripts have nothing to do with service management!</p>
74 <h2 id="allow-user-level-services">Allow User-Level Services</h2>
75 <p>If I want a given <em>user</em> to have their own services that are run <em>as</em> that user, all I need to do is have another <code>svscan</code> running as that user and pointing at another directory, which I can run as another top-level service:</p>
76 <pre><code>#!/bin/sh
77 exec 2&gt;&amp;1
78 exec setuidgid user \
79 /usr/sbin/svscan /home/user/service</code></pre>
80 <h1 id="variations">Variations</h1>
81 <p>What I described above was vanilla <code>daemontools</code>. Other systems are designed for booting entire systems with this kind of service management. Variations on this basic design add various features:</p>
82 <ul>
83 <li>The <a href="http://smarden.org/runit/"><code>runit</code></a> package extends <code>supervise</code> with the ability to execute a <code>./finish</code> script if the <code>./run</code> script fails, to do various kinds of cleanup. (<code>runit</code> renames <code>svscan</code> and <code>supervise</code> to <code>runsvdir</code> and <code>runsv</code>, respectively.)</li>
84 <li>The <a href="http://skarnet.org/software/s6/index.html"><code>s6</code></a> package adds even more options to both core programs (which are here named <code>s6-svscan</code> and <code>s6-supervise</code>) to e.g. limit the maximum number of services or modify how often scanning is done. It additionally allows control of an <code>s6-supervise</code> instance through a directory of FIFOs called <code>./event</code>.</li>
85 <li>The <a href="http://untroubled.org/daemontools-encore/"><code>daemontools-encore</code></a> package adds even more optional scripts: a <code>./start</code> script which is run before the main <code>./run</code> script and a <code>./stop</code> script after the service is disabled, a <code>./notify</code> script which is invoked when the service changes, and a few others.</li>
86 <li>The <a href="http://homepage.ntlworld.com/jonathan.deboynepollard/Softwares/nosh.html"><code>nosh</code></a> package is designed as a drop-in replacement for <code>systemd</code> on platforms where <code>systemd</code> cannot run (i.e. any Unix that is not a modern Linux) and so has a lot of utilities that superficially emulate <code>systemd</code> as well as tools which can convert <code>systemd</code> units into <code>nosh</code> service directories. <code>nosh</code> is the most radically divergent of the bunch, but is clearly a <code>daemontools</code> descendant (and incorporates most of the changes from <code>daemontools-encore</code>, as well.)</li>
87 </ul>
88 <p>Additionally, all these (except for <code>daemontools-encore</code>) have other capabilities used to set up a Unix system before starting the service-management portion.</p>
89 <h1 id="the-takeaway">The Takeaway</h1>
90 <p>The whole <code>daemontools</code> family has two properties which I really appreciate:</p>
91 <ol style="list-style-type: decimal">
92 <li>A strong commitment to never parsing anything.</li>
93 <li>A strong commitment to using Unix as a raw material.</li>
94 </ol>
95 <h2 id="why-avoid-parsing">Why avoid parsing?</h2>
96 <p>Parsing is a surprisingly difficult thing to get right. Techniques for writing parsers vary wildly in terms of how difficult they are, and <a href="http://www.cs.dartmouth.edu/~sergey/langsec/occupy/">parsing bugs are a common source</a> of <a href="https://en.wikipedia.org/wiki/Weird_machine">weird machines</a> in computer security. Various techniques can make parsing easier and less bug-prone, but it's a dangerous thing to rely on.</p>
97 <p>One way to get around this is to just skip parsing altogether. This is difficult in Unix, where most tools consume and emit plain text (or plain binary.) In other systems, such as in individual programming environments or systems like Windows PowerShell, the everything-is-plain-text requirement is relaxed, allowing tools to exchange structured data without reserializing and reparsing.</p>
98 <p>The way to avoid parsing <em>in Unix</em> is to use various kinds of structure to your advantage. Take the file system: it can, used correctly, emulate a tree-like structure or a key-value store. For example, one supplementary <code>daemontools</code> utility is <code>envdir</code>, which reads in environment variables not by parsing a string of <code>name=value</code> pairs, but by looking at a directory and turning the filename-to-file-contents mapping into a variable-name-to-variable-content mapping.</p>
99 <p>You might argue that this is silly—after all, parsing an environment variable declaration is as easy as <code>name=value</code>! Could a system really introduce a security bug in parsing something as simple as that? As it happens, <a href="https://en.wikipedia.org/wiki/Shellshock_%28software_bug%29">the answer is yes.</a></p>
100 <p>So <code>daemontools</code> avoids parsing by using directories as an organizing principle, rather than parsing configuration files.<sup><a href="#fn4" class="footnoteRef" id="fnref4">4</a></sup> This is very much a design principle in its favor.</p>
101 <h2 id="what-is-unix-as-a-raw-material">What is &quot;Unix as a raw material&quot;?</h2>
102 <p>The building blocks of <code>daemontools</code> are the parts of Unix which are common to every modern Unix variant: directories and executables and Unix processes and (in some of its descendants) FIFOs. This means you have a universe of actions you can perform outside of the <code>daemontools</code> universe:</p>
103 <ul>
104 <li>Your scripts can be written in anything you'd like, not just a shell language. You could even drop a compiled executable in, at the cost of later maintainability.</li>
105 <li>Similarly, <code>daemontools</code> services are trivially testable, because they're just plain ol' executables.</li>
106 <li>Lots of details get moved out of service management because they can be expressed in terms of other building blocks of the system. There's no need for a 'which user do I run as' configuration flag, because that can get moved into a script. (Although that script can also consult an external configuration for that, if you'd like!)</li>
107 <li>Your directories can be arranged in various ways, being split up or put back together however you'd like.<sup><a href="#fn5" class="footnoteRef" id="fnref5">5</a></sup></li>
108 </ul>
109 <p>In contrast, service management with <code>upstart</code> or <code>systemd</code> requires special configuration files and uses various other RPC mechanisms, which means that interacting with them requires using the existing tools and… isn't really otherwise possible. Testing a service with <code>upstart</code> or <code>systemd</code> requires some kind of special testing tool in order to parse the service description and set up the environment it requests. Dependency-management must be built in, and couldn't have been added in afterwards. The same goes for resource limits or process isolation. …and so forth.</p>
110 <p>&quot;Unix design&quot; has sometimes been used to justify some very poor design choices, but well-done system design that embraces the Unix building blocks in sensible ways has a lot in common with functional program design: small building blocks that have well-defined scope and semantics, well-defined side effects (if any), and fit well into a larger system by making few assumptions about what exists outside of them. <code>daemontools</code> is a perfect example of Unix design done well.</p>
111 <div class="footnotes">
112 <hr />
113 <ol>
114 <li id="fn1"><p>This one is about <code>runit</code>, not <code>daemontools</code>, but they are similar enough in principle.<a href="#fnref1">↩</a></p></li>
115 <li id="fn2"><p>It does this not using <code>inotify</code> or some other mechanism, but rather just by waking up every five seconds and doing a quick traversal of everything in the directory. This is less efficient, but also makes fewer assumptions about the platform it's running on, which means <code>daemontools</code> can run just about anywhere.<a href="#fnref2">↩</a></p></li>
116 <li id="fn3"><p>Of course, your daemon might still rely on state—but that's the fault of your daemon, and no longer inherent in the service mechanism. Contrast this to <code>sysvinit</code>-style scripts, where the only possible API is a stateful one in which the script does different things depending on the process state.<a href="#fnref3">↩</a></p></li>
117 <li id="fn4"><p>One might argue that this is a little bit disingenuous: after all, you're still invoking shell scripts! If one part of your system avoids parsing, but then you call out to a piece of software as infamously complicated and buggy as <code>bash</code>, all that other security is for naught. But there's no reason that you <em>have</em> to write your scripts in <code>bash</code>, and in fact, the creator of <code>s6</code> has built a new shell replacement for that purpose: namely, <a href="http://skarnet.org/software/execline/"><code>execline</code></a>, which is designed around both security and performance concerns. If you wanted, you could replace all those shell scripts with something else, perhaps something more like the <a href="http://shill.seas.harvard.edu/"><code>shill</code> language</a>. Luckily, the <code>daemontools</code> way is agnostic as to what it's executing, so it is easy to adopt these tools as well!<a href="#fnref4">↩</a></p></li>
118 <li id="fn5"><p>I personally tend to have a system-level <code>/etc/sv</code> for some services and a user-level <code>/home/gdritter/sv</code> for other services, regardless of whether those services are run in my user-level service tree in <code>/home/gdritter/service</code> or the root-level tree in <code>/service</code>.<a href="#fnref5">↩</a></p></li>
119 </ol>
120 </div>
121 </body>
122 </html>
1 I basically always use some program in the `daemontools` family
2 on my computers. My home laptop and desktop are booted with an
3 init system (`runit`) based on `daemontools`, while many of the
4 systems I set up elsewhere boot a vanilla distribution
5 but immediately set up a `daemontools` service directory as a
6 secondary service management tool. Quite frankly,
7 it's one of the best examples of good Unix design
8 and at this point I wouldn't want to go without it.
9
10 This is a high-level introduction to the _idea_ of `daemontools`
11 rather than a full tutorial: to learn how to set it up in practice,
12 [djb's own site](http://cr.yp.to/daemontools.html)
13 as well as
14 [a handful](http://rubyists.github.io/2011/05/02/runit-for-ruby-and-everything-else.html)[^3]
15 [of others](http://www.troubleshooters.com/linux/djbdns/daemontools_intro.htm)
16 are better references.
17
18 [^3]: This one is about `runit`, not `daemontools`, but they are
19 similar enough in principle.
20
21 # What is Daemontools?
22
23 The _core_ of `daemontools` is just two programs: `svscan` and
24 `supervise`. They're very straightforward: `svscan` takes a single
25 optional argument, and `supervise` takes a single mandatory one.
26
27 [`svscan`](http://cr.yp.to/daemontools/svscan.html)
28 watches a directory (if none is specified, then it will
29 watch the current working directory) and checks to see if new
30 directories have been added. Any time a new directory is added,
31 it starts an instance of `supervise` pointing at that new
32 directory[^1].
33
34 [^1]: It does this not using `inotify` or some other
35 mechanism, but rather just by waking up every five seconds and
36 doing a quick traversal of everything in the directory. This
37 is less efficient, but also makes fewer assumptions about the
38 platform it's running on, which means `daemontools` can run just
39 about anywhere.
40
41 And that's all that `svscan` does.
42
43 [`supervise`](http://cr.yp.to/daemontools/supervise.html)
44 switches to the supplied directory and runs a
45 script there called `./run`. If `run` stops running for _any reason_, it
46 will be started again (after a short pause, to avoid hammering the
47 system.) It will also not
48 start the `./run` script if a file called `./down` exists in the same
49 directory. Extra data about the running process gets stored
50 in a subdirectory called `supervise`, and a few other
51 tools can be used to prod and modify that data—for example, to send
52 certain signals to kill the running program, to temporarily stop it,
53 or to see how long it has been running.
54
55 And that's almost all that `supervise` does.
56
57 One extra minor wrinkle is that if `supervise` is pointed at a
58 directory that also contains a subdirectory called `./log`,
59 and `./log/run` also exists, then it will monitor that
60 executable _as well_ and point the stdout of `./run` to the stdin of
61 `./log/run`. This allows you to build a custom logging solution
62 for your services if you'd like. The `./log` directory is
63 optional.
64
65 So, how does this run a system? Well, you point `svscan` at a
66 directory that contains a subdirectory for each service you
67 want to run. Those services are generally small shell scripts
68 that call the appropriate daemon in such a way that it will
69 stay in the foreground. For example, a script to run `sshd`
70 might look like:
71
72 ~~~~
73 #!/bin/sh
74
75 # redirecting stderr to stdout
76 exec 2>&1
77
78 # the -D option keeps sshd in the foreground
79 # and the -e option writes log information to stderr
80 exec /usr/sbin/sshd -D -e
81 ~~~~
82
83 And your directory structure might look like
84
85 ~~~~
86 -+-service/
87 |-+-ngetty/
88 | |---run
89 | |-+-log/
90 | |---run
91 |-+-sshd/
92 | |---run
93 | |-+-log/
94 | |---run
95 |-+-crond/
96 | |---run
97 | |-+-log/
98 | |---run
99 ~~~~
100
101 Once you point `svscan` at this, you end up having a process
102 tree where `svscan` is managing multiple `service` instances
103 which in turn manage their respective services and logging
104 services:
105
106 ~~~~
107 -svscan-+-service-+-ngetty
108 | `-log-service
109 +-service-+-sshd
110 | `-log-service
111 +-service-+-cond
112 | `-log-service
113 ~~~~
114
115 This design has some pretty amazing practical advantages, many of
116 which are attributable to the fact that `daemontools` _is written in terms of Unix idioms_.
117 The "Unix way" gets a fair amount of derision—some well-deserved,
118 some not—but `daemontools` is a good example of how embracing the
119 idioms of your system can produce better, more flexible software.
120 Consider the following problems and their `daemontools` solutions:
121
122 ## Testing a Service Before You Start It
123
124 The `./run` script is a plain executable. If it runs and stays
125 in the foreground, doing what it should do, it's correct. If it
126 doesn't, then there's a problem. That's also the only code path,
127 which is a sharp contrast to the infamously difficult-to-write
128 `sysvinit` scripts, where `start` and `stop` and `status` and so forth
129 must all be tested in various system states[^5].
130
131 [^5]: Of course, your daemon might still rely on state—but that's
132 the fault of your daemon, and no longer inherent in the service
133 mechanism. Contrast this to `sysvinit`-style scripts, where the only possible
134 API is a stateful one in which the script does different things
135 depending on the process state.
136
137 ## Starting and Stoping a Service
138
139 All you do is create or delete a service directory. The most common
140 way of doing this is to create the service directory elsewhere, and
141 then create a symlink into the service directory to start it. This
142 lets you delete a symlink without deleting the main directory, and
143 furthermore ensures that the 'creation' of the directory is atomic.
144
145 Another tool, `svc`, lets you send signals to the running processes
146 (e.g. `svc -p` sends a `STOP` signal, and `svc -d` sends a `TERM`
147 signal as well as telling `supervise` to hold off on restarting
148 the service otherwise.)
149
150 ## Express Service Dependencies
151
152 The `daemontools` design allows for various helper tools. One of them
153 is `svok`, which finds out whether a given service is running. This
154 is just another Unix program that will exit with either `0` if
155 the process is running, or `100` if it is not. That means we can
156 write
157
158 ~~~~
159 #!/bin/sh
160 svok postgres || exit 1
161 exec 2>&1
162 exec python2 some-web-app.py
163 ~~~~
164
165 and the script will die (prompting `svscan` to wait a moment and
166 then restart it) unless `postgres` is already running.
167
168 ## Express Resource Limits
169
170 `daemontools` has several other applications that can enforce
171 various resource limits or permissions. These are not part of the
172 service mechanism—instead, they simply modify _the current
173 process_ and then `exec` some other command. That means that you
174 can easily incorporate them into a service script
175
176 ~~~~
177 #!/bin/sh
178 exec 2>&1
179 # change to the user 'sample', and then limit the stack segment
180 # to 2048 bytes, the number of open file descriptors to 2, and
181 # the number of processes to 1:
182 exec setuidgid sample \
183 softlimit -n 2048 -o 2 -p 1 \
184 some-small-daemon -n
185 ~~~~
186
187 These aren't actually special, and don't have anything to do with
188 the `daemontools` service mechanism. Any shell script
189 can incorporate `setuidgid` or `softlimit`, even if those scripts
190 have nothing to do with service management!
191
192 ## Allow User-Level Services
193
194 If I want a given _user_ to have their own services that are run
195 _as_ that user, all I need to do is have another `svscan` running as
196 that user and pointing at another directory, which I can run as another
197 top-level service:
198
199 ~~~~
200 #!/bin/sh
201 exec 2>&1
202 exec setuidgid user \
203 /usr/sbin/svscan /home/user/service
204 ~~~~
205
206 # Variations
207
208 What I described above was vanilla `daemontools`. Other systems
209 are designed for booting entire systems with this kind of service
210 management. Variations on this basic design add various features:
211
212 * The [`runit`](http://smarden.org/runit/) package
213 extends `supervise` with the ability to
214 execute a `./finish` script if the `./run` script fails, to do
215 various kinds of cleanup. (`runit` renames `svscan` and
216 `supervise` to `runsvdir` and `runsv`, respectively.)
217 * The [`s6`](http://skarnet.org/software/s6/index.html) package
218 adds even more options to both core
219 programs (which are here named `s6-svscan` and
220 `s6-supervise`) to e.g. limit the maximum number of services
221 or modify how often scanning is done. It additionally allows
222 control of an `s6-supervise` instance through a directory of
223 FIFOs called `./event`.
224 * The [`daemontools-encore`](http://untroubled.org/daemontools-encore/)
225 package adds even more optional
226 scripts: a `./start` script which is run before the main
227 `./run` script and a `./stop` script after the service is
228 disabled, a `./notify` script which is invoked when the
229 service changes, and a few others.
230 * The [`nosh`](http://homepage.ntlworld.com/jonathan.deboynepollard/Softwares/nosh.html)
231 package is designed as a drop-in replacement for
232 `systemd` on platforms where `systemd` cannot run (i.e. any
233 Unix that is not a modern Linux) and so has a lot of
234 utilities that superficially emulate `systemd` as well as
235 tools which can convert `systemd` units into `nosh` service
236 directories. `nosh` is the most radically divergent of the
237 bunch, but is clearly a `daemontools` descendant (and
238 incorporates most of the changes from `daemontools-encore`,
239 as well.)
240
241 Additionally, all these (except for `daemontools-encore`) have
242 other capabilities used to set up a Unix system before starting
243 the service-management portion.
244
245 # The Takeaway
246
247 The whole `daemontools` family has two properties which I really
248 appreciate:
249
250 1. A strong commitment to never parsing anything.
251 2. A strong commitment to using Unix as a raw material.
252
253 ## Why avoid parsing?
254
255 Parsing is a surprisingly difficult thing to get right. Techniques
256 for writing parsers vary wildly in terms of how difficult they are, and
257 [parsing bugs are a common source](http://www.cs.dartmouth.edu/~sergey/langsec/occupy/)
258 of
259 [weird machines](https://en.wikipedia.org/wiki/Weird_machine) in computer
260 security. Various techniques can make parsing easier and less
261 bug-prone, but it's a dangerous thing to rely on.
262
263 One way to get around this is to just skip parsing altogether. This
264 is difficult in Unix, where most tools consume and emit plain text
265 (or plain binary.) In other systems,
266 such as in individual programming environments or systems like
267 Windows PowerShell, the everything-is-plain-text requirement is
268 relaxed, allowing tools to exchange structured data without
269 reserializing and reparsing.
270
271 The way to avoid parsing _in Unix_ is to use various kinds of
272 structure to your advantage. Take the file system:
273 it can, used correctly, emulate a tree-like structure or a
274 key-value store. For example, one supplementary `daemontools` utility is `envdir`,
275 which reads in environment variables not by parsing a string of
276 `name=value` pairs, but by looking at a directory and turning the
277 filename-to-file-contents mapping into a variable-name-to-variable-content
278 mapping.
279
280 You might argue that this is silly—after all, parsing an environment
281 variable declaration is as easy as `name=value`! Could a system really
282 introduce a security bug in parsing something as simple as that? As it
283 happens, [the answer is yes.](https://en.wikipedia.org/wiki/Shellshock_%28software_bug%29)
284
285 So `daemontools` avoids parsing by using directories as an organizing
286 principle, rather than parsing configuration files.[^2] This is very much
287 a design principle in its favor.
288
289 [^2]: One might argue that this is a little bit disingenuous: after all,
290 you're still invoking shell scripts! If one part of your system avoids
291 parsing, but then you call out to a piece of software as infamously complicated
292 and buggy as `bash`, all that other security is for naught. But
293 there's no reason that you _have_ to write your scripts in `bash`, and in fact,
294 the creator of `s6` has built a new shell replacement for that purpose:
295 namely, [`execline`](http://skarnet.org/software/execline/), which is designed
296 around both security and performance concerns. If you wanted, you could replace
297 all those shell scripts with something else, perhaps something more like
298 the [`shill` language](http://shill.seas.harvard.edu/). Luckily, the
299 `daemontools` way is agnostic as to what it's executing, so it is
300 easy to adopt these tools as well!
301
302 ## What is "Unix as a raw material"?
303
304 The building blocks of `daemontools` are the parts of Unix which are
305 common to every modern Unix variant: directories and executables and
306 Unix processes and (in some of its descendants) FIFOs.
307 This means you have a universe of actions you can perform
308 outside of the `daemontools` universe:
309
310 - Your scripts can be written in anything you'd like, not just a
311 shell language. You could even drop a compiled executable in, at the
312 cost of later maintainability.
313 - Similarly, `daemontools` services are trivially testable, because
314 they're just plain ol' executables.
315 - Lots of details get moved out of service management because they
316 can be expressed in terms of other building blocks of the system.
317 There's no need for a 'which user do I run as' configuration flag,
318 because that can get moved into a script. (Although that script
319 can also consult an external configuration for that, if you'd like!)
320 - Your directories can be arranged in various ways, being split up
321 or put back together however you'd like.[^4]
322
323 [^4]: I personally tend to have a system-level
324 `/etc/sv` for some services and a user-level `/home/gdritter/sv` for
325 other services, regardless of whether those services are run in my
326 user-level service tree in `/home/gdritter/service` or the root-level
327 tree in `/service`.
328
329 In contrast, service management with `upstart` or `systemd` requires
330 special configuration files and uses various other RPC mechanisms,
331 which means that interacting with them requires using the existing
332 tools and… isn't really otherwise possible. Testing a service with
333 `upstart` or `systemd` requires some kind of special testing tool
334 in order to parse the service description and set up the environment
335 it requests. Dependency-management must be built in, and couldn't
336 have been added in afterwards. The same goes for resource limits
337 or process isolation. …and so forth.
338
339 "Unix design" has sometimes been used to justify some very poor
340 design choices, but well-done system design that embraces the Unix
341 building blocks in sensible ways has a lot in common with functional
342 program design: small building blocks that have well-defined
343 scope and semantics, well-defined side effects (if any), and fit
344 well into a larger system by making few assumptions about what
345 exists outside of them. `daemontools` is a perfect example of Unix
346 design done well.
1 The common mantra of Unix is that "everything is a file", and this is
2 to a mild extent true in most Unix variants, but Unix only takes that
3 concept so far. Lots of things are file-like until you prod them, and
4 then behave slightly unlike files—say, named pipes, which can sometimes
5 work as files and sometimes don't quite work as files.
6
7 The Plan 9 operating system (and its successor, Inferno) takes the
8 "everything is a file" notion much further. This is because _all_ file
9 systems are exposed via an indirect API, which means that it's quite
10 easy to build a file system by simply implementing the appropriate
11 socket communications. Linux has the ability to do this with FUSE, but
12 it's not used at all to the degree that it is in Plan 9.
13
14 I'm going to go through [...]
15
16 # Representing Data through a File System
17
18 Because you can build file systems easily, you can easily take a piece
19 of data and represent it as a file structure instead of a collection of
20 bytes. Plan 9 ships with, for example, `tarfs` and `9660srv`, which can
21 take tarballs and ISO images respectively and mount them as read-only
22 file systems.
23
24 Here is a file system I've written in 9P which takes a JSON file and
25 mounts it as a file system. Objects get represented as directories
26 with files or subdirectories named after each key. Arrays get
27 represented as directories with files or subdirectories named after
28 the indices. Everything else becomes a file containing the JSON
29 serialization of that piece of data.
30
31 $ cat sample.json
32 { "names": [ "Alouise"
33 , "Eberhardt"
34 , "Furtwangler"
35 ]
36 , "size": 3
37 }
38 $ mount.json sample.json tmp
39 $ ls -R tmp
40 tmp/
41 names size
42
43 tmp/names
44 1 2 3
45 $ cat tmp/size && echo
46 3
47 $ cat tmp/names/1 && echo
48 "Eberhardt"
49
1 I want to get away from syntax—because really, who actually likes
2 bikeshedding?—but first, I want to give the details of a few
3 syntaxes of my implementation. In particular, three different
4 languages for markup and data storage that I've used in personal
5 projects, split apart into standalone implementations in case
6 they're useful for anyone else.
7
8 I didn't invent any of these formats, but I did implement and
9 formalize them, which implicates me at least a little bit in
10 their creation. Of course, whenever you invent a markup/data
11 storage format, you should ask yourself two questions:
12
13 1. What purpose does this serve that isn't served by an existing
14 format?
15 2. What purpose does this serve that isn't served by
16 [S-expressions](http://en.wikipedia.org/wiki/S-expression)
17 specifically?
18
19 For each of these formats, I will include the following:
20
21 - A description of the abstract (parsed) representation that
22 the format describes, as well as an example of the format
23 and its corresponding abstract structure.
24 - Answers to the above questions: i.e., why does this format
25 exist, and why not use an existing format for the same
26 purpose?
27 - A _formal grammar_ which defines the language. There should
28 be no ambiguity in the resulting grammar, and in particular,
29 it should be the case that, for all `x`, `x = decode(encode(x))`.
30 Note that the opposite needn't be true (e.g. because of comments,
31 indentation, &c.)
32 - A C implementation that relies on _no external libraries_.
33 This _includes_ libc, which means that the implementations
34 _do not rely on the presence of_ `malloc`. This is done
35 by only allowing access to the parsed structure through a
36 callback, and threading the parsed structure through the
37 stack.
38 - A C implementation that relies on _no external libraries_
39 but expects the user to pass in their own allocator, which
40 can (and will be, in most cases) `malloc`, but doesn't need
41 to be.
42 - A Haskell implementation whose imports are limited to the
43 types that their APIs expose that are not included in `base`,
44 e.g. `ByteString`, `Text`, `Vector`, and in one case, `XML`.
45
46 # NML
47
48 ## Structure
49
50 All NML documents have the same abstract structure as an
51 XML document. The following NML document:
52
53 ~~~~
54 <one<two|three>|
55 four
56 <five|six>
57 >
58 ~~~~
59
60 corresponds to the following XML document:
61
62 ~~~~
63 <one two="three">
64 four
65 <five>six</five>
66 </one>
67 ~~~~
68
69 ## Why
70
71 1. NML is a format originally proposed by <Erik Naggum>, who is
72 well-known for his vituperative rants in support of Common
73 Lisp and against XML. NML was originally intended as a
74 demonstration of why you _don't_ need an attribute/element
75 distinction, but variations on it were briefly adopted by
76 <some people> as a somewhat more human-friendly way of writing
77 XML.
78
79 It was never fleshed out into a full specification by
80 Naggum—and indeed, most of the users modified it in some
81 way or other—but I have followed Naggum's examples and extended
82 it with specifications that deal with the full generality of
83 XML, including features like namespaces. (Hence, GNML, or
84 Generalized NML.) Finally, I specified
85 that GNML allows for Unicode input and in fact does not understand
86 character references at all: escaped Unicode characters in GNML
87 correspond to character references in XML, and vice versa.
88
89 My original use for GNML was as a tool for viewing and
90 producing XML, i.e. I could write NML and on-the-fly convert
91 it to valid XML that tools could understand, and dump XML
92 in pleasantly human-readable GNML. If your data will never be
93 seen by human eyes, it would be better to simply use XML—the
94 tools for working with XML are far more numerous than tools
95 for working with my particular dialect of GNML.
96
97 2. If you can use S-Expressions instead, then use S-Expressions.
98 It is in fact trivial to map S-Expressions to XML and vice
99 versa, and almost every language has mature libraries for
100 parsing and emitting S-Expressions.
101
102 ## Alternatives
103
104 - [XML]()
105 - [S-Expressions]()
106
107 ## Formal Grammar
108 ## Implementations
109
110 # NDBL
111
112 ## Structure
113
114 All NDBL documents take the form of a sequence of sequences
115 of key-value pairs, where a _key_ is a string of non-whitespace
116 characters and a _value_ is any string. NDBL source files must
117 be UTF-8 encoded. The following NDBL document:
118
119 ~~~~
120 a=b # x
121 c="d e"
122 f= g=h
123 ~~~~
124
125 corresponds to the following abstract structure expressed
126 with in Python syntax:
127
128 ~~~~
129 [ [ ("a", "b"), ("c", "d e") ]
130 , [ ("f", ""), ("g", "h") ]
131 ]
132 ~~~~
133
134 ## What
135
136 NDBL keys are always unquoted strings of Unicode characters
137 which contain neither whitespace nor the character `=`, but
138 may contain other punctuation. NDBL values are strings, which
139 may be unquoted (in which case they are terminated by
140 whitespace) or may be quoted (in which case they are terminated
141 by the first quotation mark character not preceded by a
142 backslash.) Comments are started by whitespace followed by a
143 pound sign `#` and continue to the end of a line. A new group
144 is started by a key-value pair at the beginning of a line; so
145 long as subsequent key-value pairs do not start immediately
146 after a newline, they are added to the previous group.
147
148 ## Why
149
150 1. NDBL is so-named because it is based on the configuration
151 format of Plan 9's NDB utility, with some minor modifications.
152 NDBL is a _configuration_ format, not a data storage or markup
153 format, and as such does not have a complicated internal
154 structure. In fact, the grammar of NDBL is _regular_, and could
155 be recognized by a regular expression. (It cannot be _parsed_
156 by a regular expression as that would require the ability to
157 perform a submatch under a Kleene star.)
158
159 The simplest configuration format is really just key-value
160 pairs (which is one of the reasons that environment variables
161 are often used for configuration.) NDBL adds only a bare
162 minimum on top of that: it adds a notion of grouping to
163 sequences of key-value pairs. Crucially, the groups are _not_
164 maps, because the same key may appear more than once:
165
166 ~~~~
167 # a single group with repeated keys:
168 user=terry
169 file=foo.txt
170 file=bar.txt
171 file=baz.txt
172 ~~~~
173
174 The reason NDBL might be desirable is that many other
175 configuration formats are _very_ heavyweight. TOML was
176 originally created as a response to the complexity of YAML,
177 and TOML is itself quite complicated. Simpler formats,
178 such as JSON, lack the ability to have comments—a must for
179 configuration—and many other configuration formats are
180 ad-hoc and have no formal specification (e.g. INI, which
181 TOML greatly resembles.) So, NDBL was codified as a
182 response to this.
183
184 2. You should probably just use S-Expressions.
185
186 ## Alternatives
187
188 - [YAML]()
189 - [INI]()
190 - [TOML]()
191 - [S-Expressions]()
192
193 ## Formal Grammar
194 ## Implementations
195
196 # TeLML
197
198 ## Structure
199
200 All TeLML documents take the form of sequences of _fragments_,
201 where a fragment is either a chunk of Unicode text or a
202 _TeLML tag_, which consists of a tag name and a sequence
203 of sequences of fragments.
204
205 The following TeLML document:
206
207 ~~~~
208 a \b c \d{e,\f{}, g }
209 ~~~~
210
211 Corresponds to the following abstract structure expressed
212 in Python syntax:
213
214 ~~~~
215 [ "a "
216 , Tag(name="b")
217 , " c "
218 , Tag(name="d",
219 args=[ "e"
220 , Tag(name="f",
221 args=[""])
222 , " g "
223 ])
224 ]
225 ~~~~
226
227 ## What
228
229 If a document never contains any instance of the characters
230 `\`, `{`, or `}`, then it is a TeLML document of a single text
231 fragment. Tags with an empty argument list are written as
232 `\tagname`, while tags with an argument list are followed by
233 curly braces, with individual arguments separated by commas,
234 e.g., `\tagname{arg1,arg2,...,argn}`. Curly braces without
235 a preceding tag are used for grouping, and do not appear in
236 the generated source; this may sometimes be useful to
237 separate argument-less tags from adjacent text fragments:
238 `\foobar` is a single tag called `foobar`, and `\foo{bar}`
239 is a single tag `foo` with an argument `bar`, but
240 `{\foo}bar` is two fragments: a nullary tag `foo` followed
241 by a text fragment `bar`.
242
243 ## Why
244
245 1. TeLML grew out of Markdown's lack of extensibility. What
246 I wanted was a user-extensible markup language that had
247 the ability to include rich structures
248
249 ## Alternatives
250
251 - [SGML]()
252 - [DocBook]()
253 - [MediaWiki]()
254 - [S-Expressions]()
255
256 ## Formal Grammar
257 ## Implementations
1 I read a paper recently that described a new looping construct. This
2 is admittedly pretty weird: we've pretty much settled on looping
3 constructs in imperative languages by now, and with a few exceptions
4 I don't see much change. You've got your `while` and (increasingly
5 rarely) your `do while` loop, which moves the test around:
6
7 ~~~~
8 cond -> true
9 -----------------------------------------------------------
10 while (cond) { body } -> body; while (cond) { body }
11
12 cond -> false
13 -------------------------------------
14 while (cond) { body } -> ()
15
16
17 cond -> true
18 ------------------------------------------------------------------
19 do { body } while (cond) -> body; do { body } while (cond)
20
21 cond -> false
22 ------------------------------------------
23 do { body } while (cond) -> body
24 ~~~~
25
26 We also have the nice scaffolding provided to us by `for` loops, which
27 are nice sugar for `while` loops:
28
29 ~~~~
30 for (init, iter, cond) { body } => init; do { body; iter } while (cond)
31 ~~~~
32
33 and we have flavors of `foreach` loops, which add awareness of some
34 underlying iterator protocol or collection type, which introduces an
35 element of binding
36
37 ~~~~
38 foreach (x in coll) { body } => coll.iter(); while (coll.has_next()) { x = coll.next(); body }
39 ~~~~
1 George Orwell has a famous essay called "Politics and the English
2 Language," which is widely cited, discusses, and endorsed in
3 all manner of situations. Across the intellectual landscape, in
4 many different contexts, people endorse it as wonderful writing
5 advice and an excellent critique of vapid political discourse.
6
7 Orwell puts forth several rules for clear writing, all of which
8 are motivated by a modicum of reasoning. All of them, when
9 followed to their conclusion, can only produce confused and
10 impenetrable prose. Orwell admits that he does not always
11 follow them—and of course the last rule gives the reader
12 explicit permission to violate the rules in certain
13 circumstances—but it very much makes one wonder what the point
14 of "rules" are if they are made to be broken so regularly.
15
16 Let's look at each of these six rules:
17
18 # 1. Never use a metaphor, simile, or other figure of speech which you are used to seeing in print.
19
20 Orwell's complaints about metaphor are twofold: one, that overuse
21 of these metaphors robs them of their meaning, and two, that
22 they can become divorced from their original metaphorical meaning
23 and become abstract phrases.
24
25 The first objection presupposes that meaning is necessarily
26 diminished through use, and in some cases, this may very well
27 be true. The impact of a word like _fuck_ is of course lessened
28 if it is used incessantly, and phrases that necessarily connote
29 some kind of unusualness or uniqueness—like _Achilles' Heel_,
30 which connotes a unique but absolute weakness—can suffer a
31 semantic shift such that they start to connote a less unique
32 situation.
33
34 Those, however, are special cases. In general, meaning is not a
35 quantity that diminishes with use—it is a quality that shifts,
36 and no number of essays can prevent metaphors or even just words
37 from shifting their meanings over time. This is no more or less
38 true of figures of speech than it is of words: who today uses
39 the word 'silly' to mean "blessed", or the word 'bonanza' to
40 mean "fair weather at sea"[^bonanza]?
41
42 [^bonanza]: For that matter, who today uses the word 'bonanza'?
43
44 As for the larger point: the reason that metaphors are useful
45 is because they can take a situation and explicate it via
46 similarity. A writer shouldn't overuse them, but on the other
47 hand, why avoid a common metaphor if it is appropriate to the
48 situation at hand? I could attempt to come up with an uncommon
49 metaphor to describe the "flow of money"—money is not a liquid
50 and does not flow!—but why do this when I could
1 Fulcrum was born out of the fact that I like pivot tables while I hate
2 spreadsheets.
3
4 Pivot tables, if you haven't used them, are a very nice facility in most
5 spreadsheet programs. They're an interactive facility for analyzing and
6 working with various kinds of data---you list which labels you want along
7 the columns and rows and how to reduce your data down (e.g. whether you
8 want to sum or average the numbers in question), and you can simply
9 drag-and-drop field identifiers and whatnot in order to get a new view
10 on your data. It's very nice.
11
12 Unfortunately, spreadsheet software is very tedious. Quite often, I
13 would like to do some kind of automated processing on the data I've
14 produced, and doing so is often an exercise in horrible frustration.
15 The scripting languages included with spreadsheet software are
16 of a uniformly terrible quality and the APIs provided are generally
17 quite inflexible.
18
19 So, I wrote my own from scratch in Haskell. It was a reasonably nice
20 design---it involved using the lens package to select out particular
21 fields from a data structure, and sets of reductions on the data.
22
23 In fact, it was nice enough that it took only trivial change to turn
24 it from a program for specific data processing to a library for
25 data processing in general. It is (almost) totally agnostic as to what
26 kind of data you put in and what kind of data you get out, with a few
27 caveats that I'll get into.
28
29 So, here's what Fulcrum looks like:
30
31 # A Short Example
32
33 Let's assume we have an `Employee` type as follows:
34
35 ~~~~
36 > data Department = Marketing | Sales | HR
37 > deriving (Eq, Show, Ord)
38
39 > data Employee = Employee
40 > { name :: String
41 > , birthdate :: UTCTime
42 > , dept :: Department
43 > } deriving (Eq, Show)
44 ~~~~
45
46 We want to find out the average employee age for both management and
47 non-management. (Fulcrum is overkill for this, but bear with me.)
48 First step is to find out the age of an employee _at all_, which
49 requires knowing what today is. So, we'll write a function we can
50 pass the current day to:
51
52 ~~~~
53 > getAge :: UTCTime -> Employee -> NominalDiffTime
54 > getAge today e = diffUTCTime today (birthdate e)
55 ~~~~
56
57 Now that we can find an employee's age, we can build a plan around
58 it. Again, we don't know what _today_ is, so we'll actually write
59 a function that, given a current day, will produce a plan for it:
60
61 ~~~~
62 > myPlan :: UTCTime -> Plan Employee NominalDiffTime
63 > myPlan today = Plan
64 > { planName = "Average Employee Age"
65 ~~~~
66 if we graph it, we'll use the plan name as the title. We can put
67 basically anything we want.
68 ~~~~
69 > , planFCName = "age"
70 > , planFocus = getAge
71 ~~~~
72 The focus is whatever field we'd like to extract as the relevant
73 dependent variable. We pass in a function which can take us from
74 the row type to a focus type.
75 ~~~~
76 > , planFilters = const True
77 ~~~~
78 We don't want to filter out any rows at all, so the filter function
79 just returns True to every row.
80 ~~~~
81 > , planMaps = id
82 ~~~~
83 We also don't want to do any pre-emptive modification to the data,
84 so the planMaps just return the rows unchanged.
85 ~~~~
86 > , planAxis = select department "department"
87 ~~~~
88 The `planAxis` field also takes a getter (wrapped in a `Select`
89 constructor, which I'll explain below) to select based on an
90 independent variable. In this case, we're selecting on which
91 department the employee is in, so we'll turn the record accessor
92 function into a `Getter`. The `select` function also wants to
93 know the name of the axis, for graphing and metadata purposes,
94 so we'll give it that, too.
95 ~~~~
96 > , planLines = mempty
97 ~~~~
98 If we wanted to have multiple data series on a chart, we'd use
99 the `planLines` field, but this example won't require it, so we
100 can supply it a null value. `Select` values form a monoid in
101 which combining two `Select`s produces a `Select` that produces
102 a pair of values, so their identity is an "empty" `Select` that
103 always produces `()`.
104 ~~~~
105 > , planMerge = avg
106 > }
107 ~~~~
108 And here we supply the function `avg`, which takes the average of
109 a list of numbers, as the relevant merge function.
110
111
112
113 # Plans
114
115 Fulcrum is based around the idea of a `Plan`, which is a recipe for
116 taking a dataset and producing specific values. The `Plan` type is
117 a record containing a bunch of fields which you have to specify,
118 and can produce a table-ish (if you squint) dataset in the form of
119 a map using the `runPlanToMap` function. A few auxiliary graphing
120 functions take the `Plan` and dataset directly, as well.
121
122 `Plan`s are parameterized by two types: the first is the type of the
123 individual entries in your dataset, and the second is the type of
124 the value that your plan will extract. If you're going to graph
125 your plan, the latter has to be numeric, but otherwise, it can just
126 be any Haskell type.
1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
2 <html xmlns="http://www.w3.org/1999/xhtml">
3 <head>
4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5 <meta http-equiv="Content-Style-Type" content="text/css" />
6 <meta name="generator" content="pandoc" />
7 <title></title>
8 <style type="text/css">code{white-space: pre;}</style>
9 <style type="text/css">
10 table.sourceCode, tr.sourceCode, td.lineNumbers, td.sourceCode {
11 margin: 0; padding: 0; vertical-align: baseline; border: none; }
12 table.sourceCode { width: 100%; line-height: 100%; }
13 td.lineNumbers { text-align: right; padding-right: 4px; padding-left: 4px; color: #aaaaaa; border-right: 1px solid #aaaaaa; }
14 td.sourceCode { padding-left: 5px; }
15 code > span.kw { color: #007020; font-weight: bold; }
16 code > span.dt { color: #902000; }
17 code > span.dv { color: #40a070; }
18 code > span.bn { color: #40a070; }
19 code > span.fl { color: #40a070; }
20 code > span.ch { color: #4070a0; }
21 code > span.st { color: #4070a0; }
22 code > span.co { color: #60a0b0; font-style: italic; }
23 code > span.ot { color: #007020; }
24 code > span.al { color: #ff0000; font-weight: bold; }
25 code > span.fu { color: #06287e; }
26 code > span.er { color: #ff0000; font-weight: bold; }
27 </style>
28 </head>
29 <body>
30 <p>As part of recent type-refactoring efforts in Haskell, a discussion about adding <a href="https://mail.haskell.org/pipermail/libraries/2015-March/025381.html"><code>Semigroup</code> as a parent class of <code>Monoid</code></a> has been bouncing around the mailing list. From a theoretical point of view, this is a great idea: it is more flexible than the current approach that would allow for greater expressibility.</p>
31 <p>From a <em>practical</em> point of view, however, I am inclined to oppose it. Not because it is <em>in itself</em> a bad change—it's a very reasonable change that has advantages for new code—but because I have, in the past, had to update large systems written in Haskell after GHC updates, and therefore I know that <em>this kind of change has a cost</em>. The Applicative-Monad changes seem to have made way for the Foldable-Traversable Prelude, but those have in turn inspired a number of suggestions for modifications to the Haskell standard library, each one of which, taken on its own, is reasonable, but taken as a mass, mean <em>much more work</em> for maintainers. This is <em>especially</em> true if we continue on this path of making minor refactorings at each release: each year a project will need changes, or it will quickly bit-rot beyond utility.</p>
32 <h1 id="default-superclass-instances">Default Superclass Instances</h1>
33 <p>There is, however, an alternative I would like to discuss. Some of these changes—especially the <code>Semigroup</code>/<code>Monoid</code> split—seem to involve taking the functionality of a class and splitting it into multiple smaller classes. For example, we can think of the <code>Semigroup</code>/<code>Monoid</code> change as converting</p>
34 <pre class="sourceCode haskell"><code class="sourceCode haskell"><span class="kw">class</span> <span class="dt">Monoid</span> m <span class="kw">where</span>
35 <span class="ot"> mempty ::</span> m
36 <span class="ot"> mappend ::</span> m <span class="ot">-&gt;</span> m <span class="ot">-&gt;</span> m</code></pre>
37 <p>into<sup><a href="#fn1" class="footnoteRef" id="fnref1">1</a></sup></p>
38 <pre class="sourceCode haskell"><code class="sourceCode haskell"><span class="kw">class</span> <span class="dt">Semigroup</span> m <span class="kw">where</span>
39 <span class="ot"> mappend ::</span> m <span class="ot">-&gt;</span> m <span class="ot">-&gt;</span> m
40
41 <span class="kw">class</span> <span class="dt">Semigroup</span> m <span class="ot">=&gt;</span> <span class="dt">Monoid</span> m <span class="kw">where</span>
42 <span class="ot"> mempty ::</span> m</code></pre>
43 <p><a href="https://ghc.haskell.org/trac/ghc/wiki/DefaultSuperclassInstances">Something that has been proposed before</a> (in a <a href="https://mail.haskell.org/pipermail/haskell-prime/2006-August/001587.html">few</a> <a href="https://wiki.haskell.org/Superclass_defaults">different</a> <a href="https://wiki.haskell.org/Class_system_extension_proposal">forms</a>) and which I suggest be more actively considered if changes like these are to become common is to allow <em>superclass instances to be declared within a subclass declaration</em>. This would allow you to write a single <code>instance</code> declaration for a class, and in that body <em>also include implementations for methods belong to a superclass of that class</em> by some means<sup><a href="#fn2" class="footnoteRef" id="fnref2">2</a></sup>:</p>
44 <pre class="sourceCode haskell"><code class="sourceCode haskell"><span class="kw">newtype</span> <span class="dt">MyId</span> a <span class="fu">=</span> <span class="dt">MyId</span> a
45
46 <span class="kw">instance</span> <span class="dt">Monad</span> <span class="dt">MyId</span> <span class="kw">where</span>
47 <span class="co">-- Functor method</span>
48 fmap f (<span class="dt">MyId</span> x) <span class="fu">=</span> <span class="dt">MyId</span> (f x)
49
50 <span class="co">-- Applicative methods</span>
51 pure <span class="fu">=</span> <span class="dt">MyId</span>
52 <span class="dt">MyId</span> f <span class="fu">&lt;*&gt;</span> <span class="dt">MyId</span> x <span class="fu">=</span> <span class="dt">MyId</span> (f x)
53
54 <span class="co">-- Monad methods</span>
55 return <span class="fu">=</span> <span class="dt">MyId</span>
56 <span class="dt">MyId</span> x <span class="fu">&gt;&gt;=</span> f <span class="fu">=</span> f x</code></pre>
57 <p>For the <code>Monoid</code>/<code>Semigroup</code> proposal, this would mean that any <code>Monoid</code> instances that exist would continue to work unchanged, but new instances could (optionally) split apart their declarations. Under this proposal, either of these would be acceptable:</p>
58 <pre class="sourceCode haskell"><code class="sourceCode haskell"><span class="kw">class</span> <span class="dt">Semigroup</span> m <span class="kw">where</span><span class="ot"> mappend ::</span> m <span class="ot">-&gt;</span> m <span class="ot">-&gt;</span> m
59 <span class="kw">class</span> <span class="dt">Semigroup</span> m <span class="ot">=&gt;</span> <span class="dt">Monoid</span> m <span class="kw">where</span><span class="ot"> mempty ::</span> m
60
61 <span class="co">-- combined `instance` declarations:</span>
62 <span class="kw">instance</span> <span class="dt">Monoid</span> [a] <span class="kw">where</span>
63 mempty <span class="fu">=</span> []
64 mappend <span class="fu">=</span> (<span class="fu">++</span>)</code></pre>
65 <p>or, equivalently,</p>
66 <pre class="sourceCode haskell"><code class="sourceCode haskell"><span class="kw">class</span> <span class="dt">Semigroup</span> m <span class="kw">where</span><span class="ot"> mappend ::</span> m <span class="ot">-&gt;</span> m <span class="ot">-&gt;</span> m
67 <span class="kw">class</span> <span class="dt">Semigroup</span> m <span class="ot">=&gt;</span> <span class="dt">Monoid</span> m <span class="kw">where</span><span class="ot"> mempty ::</span> m
68
69 <span class="co">-- split apart `instance` declarations</span>
70 <span class="kw">instance</span> <span class="dt">Semigroup</span> [a] <span class="kw">where</span>
71 mappend <span class="fu">=</span> (<span class="fu">++</span>)
72
73 <span class="kw">instance</span> <span class="dt">Monoid</span> [a] <span class="kw">where</span>
74 mempty <span class="fu">=</span> []</code></pre>
75 <p>And because the <code>Monoid</code> declaration for <code>[]</code> <a href="http://hackage.haskell.org/package/base-4.8.0.0/docs/src/GHC-Base.html#line-227">is already written like the former</a>, we can make the <code>Semigroup</code>/<code>Monoid</code> split without having to rewrite the instance declarations!</p>
76 <p>Because this lowers the cost of updating for new versions, various <em>other</em> useful changes might be considered that would otherwise involve far too much breakage. For example, we could consider splitting <code>Num</code> apart into small constituent parts<sup><a href="#fn3" class="footnoteRef" id="fnref3">3</a></sup>:</p>
77 <pre class="sourceCode haskell"><code class="sourceCode haskell"><span class="kw">class</span> <span class="dt">Add</span> a <span class="kw">where</span><span class="ot"> (+) ::</span> a <span class="ot">-&gt;</span> a <span class="ot">-&gt;</span> a
78 <span class="kw">class</span> <span class="dt">Sub</span> a <span class="kw">where</span><span class="ot"> (-) ::</span> a <span class="ot">-&gt;</span> a <span class="ot">-&gt;</span> a
79 <span class="kw">class</span> <span class="dt">Zero</span> a <span class="kw">where</span><span class="ot"> zero ::</span> a
80
81 <span class="kw">class</span> <span class="dt">Mul</span> a <span class="kw">where</span><span class="ot"> (*) ::</span> a <span class="ot">-&gt;</span> a <span class="ot">-&gt;</span> a
82 <span class="kw">class</span> <span class="dt">One</span> a <span class="kw">where</span><span class="ot"> one ::</span> a
83
84 <span class="kw">class</span> <span class="dt">FromInteger</span> a <span class="kw">where</span>
85 <span class="ot"> fromInteger ::</span> <span class="dt">Integer</span> <span class="ot">-&gt;</span> a
86
87 <span class="kw">instance</span> <span class="dt">Zero</span> a <span class="kw">where</span> zero <span class="fu">=</span> fromInteger <span class="dv">0</span>
88 <span class="kw">instance</span> <span class="dt">One</span> a <span class="kw">where</span> one <span class="fu">=</span> fromInteger <span class="dv">1</span>
89
90 <span class="kw">class</span> <span class="dt">Signed</span> a <span class="kw">where</span>
91 <span class="ot"> negate ::</span> a <span class="ot">-&gt;</span> a
92 <span class="ot"> abs ::</span> a <span class="ot">-&gt;</span> a
93 <span class="ot"> signum ::</span> a <span class="ot">-&gt;</span> a
94
95 <span class="kw">class</span> ( <span class="dt">Eq</span> a
96 , <span class="dt">Show</span> a
97 , <span class="dt">Add</span> a
98 , <span class="dt">Sub</span> a
99 , <span class="dt">Mul</span> a
100 , <span class="dt">FromInteger</span> a
101 , <span class="dt">Signed</span> a) <span class="ot">=&gt;</span> <span class="dt">Num</span> a <span class="kw">where</span></code></pre>
102 <p>which would allow certain numeric types to only implement a subset of the relevant operations:</p>
103 <pre class="sourceCode haskell"><code class="sourceCode haskell"><span class="kw">data</span> <span class="dt">Nat</span> <span class="fu">=</span> <span class="dt">Zero</span> <span class="fu">|</span> <span class="dt">Succ</span> <span class="dt">Nat</span>
104
105 <span class="kw">instance</span> <span class="dt">Add</span> <span class="dt">Nat</span> <span class="kw">where</span>
106 <span class="dt">Z</span> <span class="fu">+</span> y <span class="fu">=</span> s
107 <span class="dt">S</span> x <span class="fu">+</span> y <span class="fu">=</span> <span class="dt">S</span> (x <span class="fu">+</span> y)
108
109 <span class="co">{- et cetera --- but no implementations for e.g. Signed,</span>
110 <span class="co"> - which is not meaningful for `Nat`!</span>
111 <span class="co"> -}</span></code></pre>
112 <p>and also allow current <code>Num</code> functions to have a looser set of constraints than they do at present:</p>
113 <pre class="sourceCode haskell"><code class="sourceCode haskell">sum<span class="ot"> ::</span> (<span class="dt">Zero</span> a, <span class="dt">Add</span> a) <span class="ot">=&gt;</span> [a] <span class="ot">-&gt;</span> a
114 sum (x<span class="fu">:</span>xs) <span class="fu">=</span> x <span class="fu">+</span> sum xs
115 sum [] <span class="fu">=</span> zero
116
117 <span class="ot">prod ::</span> (<span class="dt">One</span> a, <span class="dt">Mul</span> a) <span class="ot">=&gt;</span> [a] <span class="ot">-&gt;</span> a
118 prod (x<span class="fu">:</span>xs) <span class="fu">=</span> x <span class="fu">*</span> prod xs
119 prod [] <span class="fu">=</span> one</code></pre>
120 <p>We could also consider splitting <code>Arrow</code><sup><a href="#fn4" class="footnoteRef" id="fnref4">4</a></sup> into distinct components:</p>
121 <pre class="sourceCode haskell"><code class="sourceCode haskell"><span class="kw">class</span> <span class="dt">Category</span> a <span class="ot">=&gt;</span> <span class="dt">Pairwise</span> a <span class="kw">where</span>
122 <span class="ot"> first ::</span> a b c <span class="ot">-&gt;</span> a (b, d) (c, d)
123 <span class="ot"> second ::</span> a b c <span class="ot">-&gt;</span> a (d, b) (d, c)
124 <span class="ot"> (***) ::</span> a b c <span class="ot">-&gt;</span> a b&#39; c&#39; <span class="ot">-&gt;</span> a (b, b&#39;) (c, c&#39;)
125 <span class="ot"> (&amp;&amp;&amp;) ::</span> a b c <span class="ot">-&gt;</span> a b c&#39; <span class="ot">-&gt;</span> a b (c, c&#39;)
126
127 <span class="kw">class</span> <span class="dt">Pairwise</span> a <span class="ot">=&gt;</span> <span class="dt">Arrow</span> a <span class="kw">where</span>
128 <span class="ot"> arr ::</span> (b <span class="ot">-&gt;</span> c) <span class="ot">-&gt;</span> a b c</code></pre>
129 <p>or even (dare I dream) splitting <code>Bits</code> into <a href="https://downloads.haskell.org/~ghc/7.8.2/docs/html/libraries/base-4.7.0.0/Data-Bits.html#t:Bits">something that is not a 22-method monstrosity</a>!</p>
130 <h1 id="potential-drawbacks">Potential Drawbacks</h1>
131 <p>On the other hand, this proposal does have some down-sides:</p>
132 <h2 id="grepping-for-instance-declarations">Grepping for Instance Declarations</h2>
133 <p>Right now, I can often find an instance declaration for a type <code>T</code> by grepping for <code>instance C T</code> (modulo some line noise) whereas with this change, it's possible that there <em>is</em> no declaration for <code>instance C T</code>, because all of <code>C</code>'s methods are declared by a subclass <code>C'</code> instead. The compiler ought to be able to deal with this without problem, which means that tools like Haddock documentation should somewhat alleviate this problem, but a user might be confused.</p>
134 <h2 id="introduces-new-possible-errors">Introduces New Possible Errors</h2>
135 <p>The declarations below are of course nonsensical, and would be rejected by the compiler—but the fact that this change would <em>introduce new failure conditions at all</em> is a drawback of the proposal.</p>
136 <pre class="sourceCode haskell"><code class="sourceCode haskell"><span class="kw">instance</span> <span class="dt">Semigroup</span> <span class="dt">Int</span> <span class="kw">where</span>
137 mappend <span class="fu">=</span> (<span class="fu">+</span>)
138
139 <span class="kw">instance</span> <span class="dt">Monoid</span> <span class="dt">Int</span> <span class="kw">where</span>
140 <span class="co">-- this conflicts with an existing declaration</span>
141 mappend <span class="fu">=</span> (<span class="fu">*</span>)
142 mempty <span class="fu">=</span> <span class="dv">1</span></code></pre>
143 <h2 id="a-pragma-less-extension">A Pragma-Less Extension</h2>
144 <p>In order to be <em>really</em> useful, we'd want to use this without a <code>LANGUAGE</code> pragma. After all, we're arguing for it on the basis of preserving backwards-compatibility, but that argument is much less compelling if we still have to change the source files to make use of it! On the other hand, that means we'd have included a GHC <em>extension</em> that takes effect despite not being enabled, which is <em>also</em> a worrying precedent!</p>
145 <p>It still might be a useful extension even if it had to be enabled by a <code>LANGUAGE</code> pragma, as it is easier to add said pragma to a source file than to manually break apart dozens of instance declarations, but it makes this feature less compelling in general.</p>
146 <h1 id="in-conclusion">In Conclusion</h1>
147 <p>As I said before, my primary objection to changes of the above nature is that they are <em>breaking</em>. I don't want to have to modify a handful of miscellaneous instance declarations on a yearly basis as people discover new levels of abstraction or become dissatisfied with current choices, especially as those changes will grow more extensive as I build more projects in Haskell! But with an extension like this, we could grow the typeclass ecosystem gradually and fix what we see as past warts <em>while maintaining backwards-compatibility</em>, which would be a very powerful tool to have.</p>
148 <div class="footnotes">
149 <hr />
150 <ol>
151 <li id="fn1"><p>This is perhaps more simplistic than we want: we can also use the existing <code>Semigroup</code> class from <a href="http://hackage.haskell.org/package/Semigroup-0.0.7/docs/Data-Semigroup.html#t:Semigroup">the <code>semigroup</code> package</a> and then, in the <code>Monoid</code> class declaration, explain how to derive the methods of the superclass. This would look like:</p>
152 <pre class="sourceCode haskell"><code class="sourceCode haskell"><span class="kw">class</span> <span class="dt">Semigroup</span> m <span class="ot">=&gt;</span> <span class="dt">Monoid</span> m <span class="kw">where</span>
153 <span class="ot"> mempty ::</span> m
154 <span class="ot"> mappend ::</span> m <span class="ot">-&gt;</span> m <span class="ot">-&gt;</span> m
155
156 <span class="kw">instance</span> <span class="dt">Semigroup</span> m <span class="kw">where</span>
157 (<span class="fu">.++.</span>) <span class="fu">=</span> mappend</code></pre>
158 <p>The example above is slightly simpler, which is why I relegated this version to a footnote.<a href="#fnref1">↩</a></p></li>
159 <li id="fn2"><p>This isn't a concrete proposal, so maybe the actual syntax or semantics of these things should be changed! I want to focus on the <em>feature</em> and not the <em>instantiation</em>.<a href="#fnref2">↩</a></p></li>
160 <li id="fn3"><p>For this example, I added <code>Zero</code> and <code>One</code> classes so that a given type might implement an additive and multiplicative unit while not necessarily having a sensible <code>FromInteger</code> implementation. For example, it might not make sense to implement <code>fromInteger</code> for a complex number, but complex numbers clearly have an additive unit:</p>
161 <pre class="sourceCode haskell"><code class="sourceCode haskell"><span class="kw">data</span> <span class="dt">Complex</span> <span class="fu">=</span> <span class="dt">Complex</span> <span class="dt">Float</span> <span class="dt">Float</span>
162 <span class="kw">deriving</span> (<span class="dt">Eq</span>, <span class="dt">Show</span>)
163
164 <span class="kw">instance</span> <span class="dt">Zero</span> <span class="dt">Complex</span> <span class="kw">where</span>
165 zero <span class="fu">=</span> <span class="dt">Complex</span> (<span class="dv">0</span><span class="fu">.</span><span class="dv">0</span>, <span class="dv">0</span><span class="fu">.</span><span class="dv">0</span>)</code></pre>
166 <p>This means that the <code>Sum</code> and <code>Product</code> monoids could be rewritten like:</p>
167 <pre class="sourceCode haskell"><code class="sourceCode haskell"><span class="kw">newtype</span> <span class="dt">Product</span> a <span class="fu">=</span> <span class="dt">Product</span> {<span class="ot"> getProduct ::</span> a }
168 <span class="kw">deriving</span> (<span class="dt">Eq</span>, <span class="dt">Show</span>)
169
170 <span class="kw">instance</span> (<span class="dt">One</span> a, <span class="dt">Mult</span> a) <span class="ot">=&gt;</span> <span class="dt">Monoid</span> (<span class="dt">Product</span> a) <span class="kw">where</span>
171 mempty <span class="fu">=</span> <span class="dt">Product</span> one
172 x <span class="ot">`mappend`</span> y <span class="fu">=</span> <span class="dt">Product</span> (getProduct x <span class="fu">*</span> getProduct y)</code></pre>
173 <p>Notice that I added <code>Zero</code> and <code>One</code> in such a way that an existing <code>Num</code> instance declaration will not have to change anything to implement those classes!<a href="#fnref3">↩</a></p></li>
174 <li id="fn4"><p>I have on numerous occasions had reason to use the <code>Arrow</code> abstraction, but haven't had a sensible implementation of <code>arr</code>. To use a contrived example, I could define a GADT that can describe the structure of boolean circuits in a way that resembles an Arrow, but has no way of expressing <code>arr</code>:</p>
175 <pre class="sourceCode haskell"><code class="sourceCode haskell"><span class="kw">data</span> <span class="dt">Circ</span> a b <span class="kw">where</span>
176 <span class="dt">BoolFunc</span><span class="ot"> ::</span> (<span class="dt">Bool</span> <span class="ot">-&gt;</span> <span class="dt">Bool</span>) <span class="ot">-&gt;</span> <span class="dt">Circ</span> <span class="dt">Bool</span> <span class="dt">Bool</span>
177 <span class="dt">Ident</span><span class="ot"> ::</span> <span class="dt">Circ</span> a a
178 <span class="dt">Compose</span><span class="ot"> ::</span> <span class="dt">Circ</span> a b <span class="ot">-&gt;</span> <span class="dt">Circ</span> b c <span class="ot">-&gt;</span> <span class="dt">Circ</span> a c
179 <span class="dt">First</span><span class="ot"> ::</span> <span class="dt">Circ</span> a b <span class="ot">-&gt;</span> <span class="dt">Circ</span> (a, d) (b, d)
180 <span class="dt">Second</span><span class="ot"> ::</span> <span class="dt">Circ</span> a b <span class="ot">-&gt;</span> <span class="dt">Circ</span> (d, a) (d, b)
181 <span class="dt">Parallel</span><span class="ot"> ::</span> <span class="dt">Circ</span> a b <span class="ot">-&gt;</span> <span class="dt">Circ</span> a&#39; b&#39; <span class="ot">-&gt;</span> <span class="dt">Circ</span> (a, a&#39;) (b, b&#39;)
182 <span class="dt">Fanout</span><span class="ot"> ::</span> <span class="dt">Circ</span> a b <span class="ot">-&gt;</span> <span class="dt">Circ</span> a b&#39; <span class="ot">-&gt;</span> <span class="dt">Circ</span> a (b, b&#39;)
183
184 <span class="kw">instance</span> <span class="dt">Category</span> <span class="dt">Circ</span> <span class="kw">where</span>
185 id <span class="fu">=</span> <span class="dt">Ident</span>
186 (<span class="fu">.</span>) <span class="fu">=</span> flip <span class="dt">Compose</span>
187
188 <span class="kw">instance</span> <span class="dt">Arrow</span> <span class="dt">Circ</span> <span class="kw">where</span>
189 first <span class="fu">=</span> <span class="dt">First</span>
190 second <span class="fu">=</span> <span class="dt">Second</span>
191 (<span class="fu">***</span>) <span class="fu">=</span> <span class="dt">Parallel</span>
192 (<span class="fu">&amp;&amp;&amp;</span>) <span class="fu">=</span> <span class="dt">Fanout</span>
193 arr <span class="fu">=</span> error <span class="st">&quot;Nothing sensible to put here!&quot;</span>
194
195 <span class="co">-- for example, using this definition, we can write xor:</span>
196 <span class="ot">xor ::</span> <span class="dt">BoolCircuit</span> (<span class="dt">Bool</span>, <span class="dt">Bool</span>) <span class="dt">Bool</span>
197 xor <span class="fu">=</span> ((first <span class="dt">Not</span> <span class="fu">&gt;&gt;&gt;</span> <span class="dt">And</span>) <span class="fu">&amp;&amp;&amp;</span> (second <span class="dt">Not</span> <span class="fu">&gt;&gt;&gt;</span> <span class="dt">And</span>)) <span class="fu">&gt;&gt;&gt;</span> <span class="dt">Or</span>
198
199 <span class="co">-- ...and using xor, write a half-adder:</span>
200 <span class="ot">halfAdder ::</span> <span class="dt">BoolCircuit</span> (<span class="dt">Bool</span>, <span class="dt">Bool</span>) (<span class="dt">Bool</span>, <span class="dt">Bool</span>)
201 halfAdder <span class="fu">=</span> xor <span class="fu">&amp;&amp;&amp;</span> <span class="dt">And</span></code></pre>
202 <p>This is not an unreasonable definition—it would be nice to abstract over such a definition using existing tools—but the structure of the <code>Arrow</code> typeclass makes it difficult!<a href="#fnref4">↩</a></p></li>
203 </ol>
204 </div>
205 </body>
206 </html>
1 As part of recent type-refactoring efforts in Haskell,
2 a discussion about adding
3 [`Semigroup` as a parent class of `Monoid`](https://mail.haskell.org/pipermail/libraries/2015-March/025381.html)
4 has been bouncing around the mailing list.
5 From a theoretical point of view, this is a great idea:
6 it is more flexible than the current approach that would allow
7 for greater expressibility.
8
9 From a _practical_ point of view, however, I am inclined to oppose it.
10 Not because it is _in itself_ a bad change—it's a very reasonable
11 change that has advantages for new code—but
12 because I have, in the past, had to update large systems
13 written in Haskell after GHC updates, and therefore I know that
14 _this kind of change has a cost_. The Applicative-Monad changes
15 seem to have made way for the Foldable-Traversable Prelude, but
16 those have in turn inspired a number of suggestions for
17 modifications to the Haskell standard library, each one of which,
18 taken on its own, is reasonable, but taken as a mass, mean _much
19 more work_ for maintainers. This is _especially_ true if we continue
20 on this path of making minor refactorings at each release: each year
21 a project will need changes, or it will quickly bit-rot beyond
22 utility.
23
24 # Default Superclass Instances
25
26 There is, however, an alternative I would like to discuss.
27 Some of these changes—especially the `Semigroup`/`Monoid`
28 split—seem to involve taking the functionality of
29 a class and splitting it into multiple smaller classes. For
30 example, we can think of the `Semigroup`/`Monoid` change as
31 converting
32
33 ~~~~{.haskell}
34 class Monoid m where
35 mempty :: m
36 mappend :: m -> m -> m
37 ~~~~
38
39 into[^semi]
40
41 ~~~~{.haskell}
42 class Semigroup m where
43 mappend :: m -> m -> m
44
45 class Semigroup m => Monoid m where
46 mempty :: m
47 ~~~~
48
49 [Something that has been proposed before](https://ghc.haskell.org/trac/ghc/wiki/DefaultSuperclassInstances)
50 (in a [few](https://mail.haskell.org/pipermail/haskell-prime/2006-August/001587.html)
51 [different](https://wiki.haskell.org/Superclass_defaults)
52 [forms](https://wiki.haskell.org/Class_system_extension_proposal))
53 and which I suggest be
54 more actively considered if changes like these are to become
55 common is to allow _superclass instances to be declared within
56 a subclass declaration_. This would allow you to write a single
57 `instance` declaration for a class, and in that body _also include
58 implementations for methods belong to a superclass of that
59 class_ by some means[^note]:
60
61 [^note]: This isn't a concrete proposal, so maybe the actual syntax
62 or semantics of these things should be changed! I want to focus
63 on the _feature_ and not the _instantiation_.
64
65 ~~~~{.haskell}
66 newtype MyId a = MyId a
67
68 instance Monad MyId where
69 -- Functor method
70 fmap f (MyId x) = MyId (f x)
71
72 -- Applicative methods
73 pure = MyId
74 MyId f <*> MyId x = MyId (f x)
75
76 -- Monad methods
77 return = MyId
78 MyId x >>= f = f x
79 ~~~~
80
81 For the `Monoid`/`Semigroup` proposal, this would mean that any
82 `Monoid` instances that exist would continue to
83 work unchanged, but new instances could (optionally) split apart
84 their declarations. Under this proposal, either of these would
85 be acceptable:
86
87 ~~~~{.haskell}
88 class Semigroup m where mappend :: m -> m -> m
89 class Semigroup m => Monoid m where mempty :: m
90
91 -- combined `instance` declarations:
92 instance Monoid [a] where
93 mempty = []
94 mappend = (++)
95 ~~~~
96
97 or, equivalently,
98
99 ~~~~{.haskell}
100 class Semigroup m where mappend :: m -> m -> m
101 class Semigroup m => Monoid m where mempty :: m
102
103 -- split apart `instance` declarations
104 instance Semigroup [a] where
105 mappend = (++)
106
107 instance Monoid [a] where
108 mempty = []
109 ~~~~
110
111 And because the `Monoid` declaration for `[]`
112 [is already written like the former](http://hackage.haskell.org/package/base-4.8.0.0/docs/src/GHC-Base.html#line-227),
113 we can make the `Semigroup`/`Monoid` split without having to rewrite
114 the instance declarations!
115
116 Because this lowers the cost of updating for new versions, various
117 _other_ useful changes might be considered that would otherwise
118 involve far too much breakage. For example, we could consider
119 splitting `Num` apart into small constituent parts[^num]:
120
121 ~~~~{.haskell}
122 class Add a where (+) :: a -> a -> a
123 class Sub a where (-) :: a -> a -> a
124 class Zero a where zero :: a
125
126 class Mul a where (*) :: a -> a -> a
127 class One a where one :: a
128
129 class FromInteger a where
130 fromInteger :: Integer -> a
131
132 instance Zero a where zero = fromInteger 0
133 instance One a where one = fromInteger 1
134
135 class Signed a where
136 negate :: a -> a
137 abs :: a -> a
138 signum :: a -> a
139
140 class ( Eq a
141 , Show a
142 , Add a
143 , Sub a
144 , Mul a
145 , FromInteger a
146 , Signed a) => Num a where
147 ~~~~
148
149 which would allow certain numeric types to only implement a
150 subset of the relevant operations:
151
152 ~~~~{.haskell}
153 data Nat = Zero | Succ Nat
154
155 instance Add Nat where
156 Z + y = s
157 S x + y = S (x + y)
158
159 {- et cetera --- but no implementations for e.g. Signed,
160 - which is not meaningful for `Nat`!
161 -}
162 ~~~~
163
164 and also allow current `Num` functions to have a looser set of
165 constraints than they do at present:
166
167 ~~~~{.haskell}
168 sum :: (Zero a, Add a) => [a] -> a
169 sum (x:xs) = x + sum xs
170 sum [] = zero
171
172 prod :: (One a, Mul a) => [a] -> a
173 prod (x:xs) = x * prod xs
174 prod [] = one
175 ~~~~
176
177 We could also consider splitting `Arrow`[^arr] into distinct
178 components:
179
180 ~~~~{.haskell}
181 class Category a => Pairwise a where
182 first :: a b c -> a (b, d) (c, d)
183 second :: a b c -> a (d, b) (d, c)
184 (***) :: a b c -> a b' c' -> a (b, b') (c, c')
185 (&&&) :: a b c -> a b c' -> a b (c, c')
186
187 class Pairwise a => Arrow a where
188 arr :: (b -> c) -> a b c
189 ~~~~
190
191 or even (dare I dream) splitting `Bits` into
192 [something that is not a 22-method monstrosity](https://downloads.haskell.org/~ghc/7.8.2/docs/html/libraries/base-4.7.0.0/Data-Bits.html#t:Bits)!
193
194 # Potential Drawbacks
195
196 On the other hand, this proposal does have some down-sides:
197
198 ## Grepping for Instance Declarations
199
200 Right now, I can often find an instance declaration for a type `T` by
201 grepping for `instance C T` (modulo some line noise) whereas with this
202 change, it's possible that there _is_ no declaration for
203 `instance C T`, because all of `C`'s methods are declared by a
204 subclass `C'` instead. The compiler ought to be able to deal with
205 this without problem, which means that tools like Haddock documentation
206 should somewhat alleviate this problem, but a user might be confused.
207
208 ## Introduces New Possible Errors
209
210 The declarations below are of course nonsensical, and would be
211 rejected by the compiler—but the fact that this change would
212 _introduce new failure conditions at all_ is a drawback
213 of the proposal.
214
215 ~~~~{.haskell}
216 instance Semigroup Int where
217 mappend = (+)
218
219 instance Monoid Int where
220 -- this conflicts with an existing declaration
221 mappend = (*)
222 mempty = 1
223 ~~~~
224
225 ## A Pragma-Less Extension
226
227 In order to be _really_ useful, we'd want to use this without a
228 `LANGUAGE` pragma. After all, we're arguing for it on the basis of
229 preserving backwards-compatibility, but that argument is much less
230 compelling if we still have to change the source files to make use
231 of it! On the other hand, that means we'd have included a GHC
232 _extension_ that takes effect despite not being enabled, which
233 is _also_ a worrying precedent!
234
235 It still might be a useful extension even if it had to be enabled by
236 a `LANGUAGE` pragma, as it is easier to add said pragma to a source
237 file than to manually break apart dozens of instance declarations,
238 but it makes this feature less compelling in general.
239
240 # In Conclusion
241
242 As I said before, my primary objection to changes of the above nature is
243 that they are _breaking_. I don't want to have to modify a handful of
244 miscellaneous instance declarations on a yearly basis as people
245 discover new levels of abstraction or become dissatisfied with current
246 choices, especially as those changes will grow more extensive
247 as I build more projects in Haskell! But with an extension like this,
248 we could grow the typeclass ecosystem gradually and fix what we see
249 as past warts _while maintaining backwards-compatibility_, which
250 would be a very powerful tool to have.
251
252 [^arr]: I have on numerous occasions had reason to use the
253 `Arrow` abstraction, but haven't had a sensible implementation
254 of `arr`. To use a contrived example, I could define a GADT that
255 can describe the structure of boolean circuits in a way that
256 resembles an Arrow, but has no way of expressing `arr`:
257
258 ~~~~{.haskell}
259 data Circ a b where
260 BoolFunc :: (Bool -> Bool) -> Circ Bool Bool
261 Ident :: Circ a a
262 Compose :: Circ a b -> Circ b c -> Circ a c
263 First :: Circ a b -> Circ (a, d) (b, d)
264 Second :: Circ a b -> Circ (d, a) (d, b)
265 Parallel :: Circ a b -> Circ a' b' -> Circ (a, a') (b, b')
266 Fanout :: Circ a b -> Circ a b' -> Circ a (b, b')
267
268 instance Category Circ where
269 id = Ident
270 (.) = flip Compose
271
272 instance Arrow Circ where
273 first = First
274 second = Second
275 (***) = Parallel
276 (&&&) = Fanout
277 arr = error "Nothing sensible to put here!"
278
279 -- for example, using this definition, we can write xor:
280 xor :: BoolCircuit (Bool, Bool) Bool
281 xor = ((first Not >>> And) &&& (second Not >>> And)) >>> Or
282
283 -- ...and using xor, write a half-adder:
284 halfAdder :: BoolCircuit (Bool, Bool) (Bool, Bool)
285 halfAdder = xor &&& And
286 ~~~~
287
288 This is not an unreasonable definition—it would be nice to
289 abstract over such a definition using existing tools—but the structure
290 of the `Arrow` typeclass makes it difficult!
291
292 [^num]: For this example, I added `Zero` and `One` classes so that a
293 given type might implement an additive and multiplicative unit while
294 not necessarily having a sensible `FromInteger` implementation. For
295 example, it might not make sense to implement `fromInteger` for a
296 complex number, but complex numbers clearly have an additive unit:
297
298 ~~~~{.haskell}
299 data Complex = Complex Float Float
300 deriving (Eq, Show)
301
302 instance Zero Complex where
303 zero = Complex (0.0, 0.0)
304 ~~~~
305
306 This means that the `Sum` and `Product` monoids could be rewritten like:
307
308 ~~~~{.haskell}
309 newtype Product a = Product { getProduct :: a }
310 deriving (Eq, Show)
311
312 instance (One a, Mult a) => Monoid (Product a) where
313 mempty = Product one
314 x `mappend` y = Product (getProduct x * getProduct y)
315 ~~~~
316
317 Notice that I added `Zero` and `One` in such a way that an existing
318 `Num` instance declaration will not have to change anything to implement
319 those classes!
320
321 [^semi]: This is perhaps more simplistic than we want: we can also use
322 the existing `Semigroup` class from
323 [the `semigroup` package](http://hackage.haskell.org/package/Semigroup-0.0.7/docs/Data-Semigroup.html#t:Semigroup)
324 and then, in the `Monoid` class declaration, explain how to derive the
325 methods of the superclass. This would look like:
326
327 ~~~~{.haskell}
328 class Semigroup m => Monoid m where
329 mempty :: m
330 mappend :: m -> m -> m
331
332 instance Semigroup m where
333 (.++.) = mappend
334 ~~~~
335
336 The example above is slightly simpler, which is why I relegated this
337 version to a footnote.
1 I did not like any of the S-Expression libraries that are available for
2 Haskell, for various reasons. Sometimes the API was quite poor. Sometimes
3 the implementation was wonky. Sometimes it was just a license issue.
4 Whatever it was, I felt like it was possible to do better.
5
6 So I did.
7
8 In the process, I _completely_ over-engineered the library. It does a
9 lot, perhaps more than necessary. It understands several flavors of
10 S-Expressions (Common Lisp's, Scheme's, Rivest's, and Clojure's),
11 exposing them in different ways depending on how the consumer wants
12 to use them, with optional support for various reader macros. It
13 should be easy for a library user to extend it in various ways.
14
15 That said, I hope it should be useful enough!
16
17 # S-Expression Representations
18
19 S-Expressions are generally built up of cons pairs, which are written
20
21 ~~~~
22 ( x . y )
23 ~~~~
24
25 and empty lists, which can be written differently depending on the
26 language
27
28 ~~~~
29 NIL ;; Common Lisp
30 () ;; also Common Lisp
31 '() ;; Scheme, which is sugar for
32 (quote ()) ;; also Scheme
33 ;; etc.
34 ~~~~
35
36 So one way of exposing an S-Expression based API is to use a data type
37 like:
38
39 ~~~~{.haskell}
40 data SExpr
41 = Atom {- some atom representation -}
42 | Cons SExpr Sexpr
43 | Nil
44 ~~~~
45
46 …but this also isn't how S-Expressions are usually used. You rarely see
47 dotted pairs in practice; instead, most S-Expressions are well-formed
48 lists, which are sugar for sequences of cons pairs that end in an
49 empty list:
50
51 ~~~~
52 (a b c d) === (a . (b . (c . (d . ()))))
53 ~~~~
54
55 So perhaps we want this representation:
56
57 ~~~~{.haskell}
58 data SExpr
59 = Atom {- ... -}
60 | List [SExpr]
61 ~~~~
62
63 …but while this is acceptable for some uses, this API means we can't
64 address the presence of dotted lists at all! So perhaps we should
65 include them as a special case, making them easy to throw away in
66 processing:
67
68 ~~~~{.haskell}
69 data SExpr
70 = Atom {- ... -}
71 | List [SExpr] -- for (a b ... c)
72 | DotList [SExpr] Sexpr -- for (a b ... c . d)
73 ~~~~
74
75 [library] always uses the first representation internally, but
76 exposes a version of each of these data types. The first is
77 called `SExpr`, while the latter two are called `WellFormedSExpr`
78 and `RichSExpr`. It also exposes, for each of these, a set of
79 pattern synonyms so that they can be used interchangeably.
80 For example, the code will work regardless of which S-Expression
81 type is being used:
82
83 ~~~~{.haskell}
84 import Data.SExpression.Repr.{- any -}
85
86 sum (Atom n) = n
87 sum (Cons x y) = sum x + sum y
88 sum Nil = 0
89 ~~~~
90
91 # Which Atoms?
92
93 There is no "standard S-Expression" grammar, and the various kinds of
94 S-Expressions understood by different tools have general commonality
95 but can vary wildly in the specifics. For example, the data types
96 understood by Common Lisp and Scheme are broadly similar but have
97 mild differences, while the Rivest Internet Draft about S-Expressions
98 as a transport mechanism has a very idiosyncratic set of atom types
99 used to represent octet strings.
100
101 [Library] does not specify one, but instead exposes several. The
102 modules `Data.SExpression.CommonLisp`, `Data.SExpression.Scheme`,
103 and `Data.SExpression.Rivest` all understand various flavors of
104 atoms: each defines its own `Atom` type and functions to parse
105 and serialize S-expressions using that `Atom`.
106
107 However, the machinery of S-expressions themselves (i.e. the part
108 that is basically just nested parens) is generic and shared
109 between the implementations. The `SExpr` type is parameterized
110 by an atom type, which could be one of the built-in atom types:
111
112 ~~~~
113 Prelude> import qualified Data.SExpression.CommonLisp as CL
114 Prelude CL> import qualified Data.SExpression.Scheme as S
115 Prelude CL S> :t CL.parse
116 CL.parse :: Text -> SExpr CL.Atom
117 Prelude CL S> :t S.parse
118 S.parse :: Text -> SExpr S.Atom
119 ~~~~
120
121 Additionally, interfaces are present which would allow a user to
122 extend the library with their own notion of an atom. We can use
123 any type we want as an atom, so long as we can provide an
124 AttoParsec parser for it and a printer for it, we can build
125 up a `SExprSpec` value, which describes a custom S-expression
126 format:
127
128 ~~~~{.haskell}
129 import Control.Applicative
130 import Data.SExpression.General
131 import Data.Text (Text, pack)
132
133 mySpec :: SExprSpec SExpr Int
134 mySpec = mkSExprSpec parseNum showNum
135 where parseNum = read <$> many1 digit
136 showNum = pack . show
137
138 myParse :: Text -> Either String (SExpr Int)
139 myParse = parseGeneral mySpec
140
141 myEmit :: SExpr Int -> Text
142 myEmit = emitGeneral mySpec
143
144 myParseRich :: Text -> Either String (RichSExpr Int)
145 myParseRich = parseGeneral (toRich mySpec)
146
147 main = do
148 assert $ myParse "(1 2 3)" == SCons 1 (SCons 2 (SCons 3 Nil))
149 assert $ pack "(1 2 3)" == myEmit (SCons 1 (SCons 2 (SCons 3 Nil)))
150 ~~~~
151
152 For the most part, you shouldn't need to do this, but it is possible
153 in case you'd want to.
154
155 # Reader Macros
156
157 Simple S-expressions are a common data storage format, but lots of
158 Lisp-based uses of S-expressions include some facility for
159 _reader macros_ as well. The most commonly seen reader macro in
160 most Lisps is introduce by the quote, which allows you to execute
161 a
1 In this post, I'm going to argue that dynamic typing is not an inferior
2 tool that is superseded by strong static typing, but rather an important
3 tool in the toolkit of a programmer, and that different problem domains
4 are best served by different tool sets.
5
6 # Defining Terms
7
8 These discussions tend to result in a lot of tedium because of
9 terminological confusion, so let's start with this:
10
11 - *Static Typing* means that a programming language will reject
12 certain programs as syntactically valid but semantically anomalous
13 according to a 'type system', thus preventing programs which
14 cause certain classes of run-time errors from ever being run.
15
16 - *Dynamic Typing* means that a programming language will execute
17 syntactically valid programs, allowing those programs to execute
18 until run-time errors are found, generally based on run-time values
19 which correspond to the type of a value. Bob Harper, following in
20 the tradition of Dana Scott, insists on calling these
21 [http://existentialtype.wordpress.com/2011/03/19/dynamic-languages-are-static-languages/](unityped languages),
22 but I will stick with the more common nomeclature.
23
24 - I to some degree make a tripartite distinction between languages
25 with dynamic types, static types, and *dependent types*, despite the
26 fact that
27 dependent types are a kind of static type. A *dependent type system*
28 allows types to depend on values, and I make the distinction because
29 programming in a dependently typed system has a very different flavor
30 than programming in a static but non-dependently-typed system.
31
32 # Why Do We Want To Use Type Systems?
33
34 Before defending dynamic languages, I want to start with something
35 basic: why do we want static languages in the first place?
36
37 Type systems are a lightweight but formal system for reasoning about
38 the correctness of programs. A type system is there to help the
39 programmer write correct programs by preventing various classes of
40 errors. Different type systems can catch different classes of
41 errors: for example, even a very weak type system like C's will warn
42 you if you attempt to take the square root of a string:
43
44 ~~~~
45 tmp.c:5:5: error: incompatible type for argument 1 of 'sqrt'
46 ...
47 note: expected 'double' but argument is of type 'char *'
48 ~~~~
49
50 More and more powerful type systems can rule out more and more
51 classes of bugs, while at the same time allowing programs that
52 might be rejected with other type systems. Most ML-ish functional
53 languages make it impossible to execute programs which would
54 result in null pointer exceptions, which are still permitted by
55 Java's type system. Haskell's type system makes a distinction
56 between code that performs side effects and code that doesn't,
57 and doesn't allow calling the former from the latter. Rust uses
58 its type system to ensure memory safety while avoiding garbage
59 collection.
60
61 At the far end of the spectrum, there are the dependently
62 typed languages, which allow you to write executable mathematical
63 objects that prevent errors in a way which is impossible to express in
64 "weaker" statically typed languages. You encode your specification,
65 then you write your program, and if your program doesn't fit
66 the specification, it will fail to compile. This is sometimes
67 considered an ultimate goal of strongly statically typed languages:
68 the ability to express all errors as type errors so that they can
69 be statically prevented.
70
71 There are other advantages, as well: one is performance-related. The
72 more information we have about our program at _compile_ time,
73 the less we need to know about it at _run_ time. If our program is
74 constructed in such a way that we cannot dereference a null pointer, we
75 don't need to check for null before dereferences. If our program
76 is constructed in such a way that a given value is always a machine integer,
77 we can perform machine addition without first checking an associated
78 runtime tag. If our program is constructed in such a way that a given
79 list is non-zero, we can omit runtime length checks.
80
81 Another is ergonomic: people who are new to typed languages will talk
82 about "fighting the type system", but people who are comfortable with
83 static type systems often express the opposite, using the types as a
84 way of guiding you towards correct solutions. A basic practical example
85 is using types as a way of distinguishing sanitized from unsanitized
86 strings to prevent SQL injection. What was once a matter of thorough
87 testing and careful code discipline becomes simple: if you do it wrong,
88 your compiler will tell you.
89
90 All of this is to say: type systems are wonderful! They are an invaluable
91 tool for writing code and verifying it correct, and I hope that everyone
92 learns and uses at least one strong statically-typed language.
93
94 # Why Might You _Not_ Want To Use Type Systems?
95
96 I can think of three big reasons:
97
98 - For certain problems, _stronger types are a hurdle to expressing the
99 program you want to express_.
100 - Type systems are _not always amenable to the type discipline you
101 want to impose_.
102 - A program _can still perform correctness checks without a static
103 type system_.
104
105 Let's go into these in detail:
106
107 ## Expressing The Program You Want To Express
108
109 Some problems are very amenable to being expressed in a static setting!
110 It's not uncommon to develop in a static system by writing a bunch of type
111 signatures and gradually filling in their definitions, fitting pieces
112 together as a kind of framework and then adding what's missing to the
113 framework.
114
115 On the other hand, sometimes this is less helpful. Certain types are
116 difficult to express in a static way, and appeasing the typechecker
117 requires extra work which distracts from the program at hand. As
118 Bob Harper informs us, dynamic languages can be seen as the subset
119 of static languages in which all types have a single type. Among other
120 things, this means that dynamic languages often have idioms well-suited
121 to dispatch on a large or even unbounded set of runtime tags.
122 Consequently, if your problem involves dealing with data in which you
123 make decisions based on a lot of runtime characteristics (such as in
124 scripting or shell invocation) it might be a good idea to take advantage
125 of the structure afforded by dynamic languages!
126
127 One could, as Harper suggests, construct that unitype in a static
128 language—but look at the tedium involved in, say,
129 [manipulating JSON in Haskell](), and you can see that, at that point,
130 you're not writing the program you want: you're writing the boilerplate
131 to deal with the unitype. Your intention might be clear to you, but
132 you also have to look through layers of constructors and types that
133 are there mostly because the compiler needs them to be assured that
134 your program is safe. (For a great example of this, look at the
135 library I wrote for working with [Activity Steams]
136
137 Additionally, there is a []
138
139 ## The Type Discipline You Want To Impose
140
141
142
143 # A Short Story From Real Life
144
145 A few months ago at my work, we did what amounted to a rewrite of a compiler
146 system that was written in Haskell. Haskell's type system is inching
147 towards being fully dependently typed, and already it can express
148 vast swaths of what dependent types can achieve. We took full
149 advantage of this, and the end result was pretty amazing. The AST was
150 constructed in such a way that the types expressed not just the result
151 types of the AST nodes, but also information about the use of those
152 values, and prevented invalid ASTs from being constructed at any
153 time.
154
155 As this system was being constructed, the type machinery was nothing
156 short of invaluable. Multiple times, it caught subtle errors that would
157 have likely gone unnoticed for long periods of time. It ensured
158 end-to-end correctness of the program being written as we were writing
159 it. Because of our chosen types, we were literally incapable of
160 constructing invalid programs!
161
162 But as we continued to expand our compiler, this system started to be less
163 and less helpful. It was getting in the way of optimizations:
164 constructing the AST, in effect, required type machinery to 'prove'
165 it was constructed correctly, so certain optimization passes
166 required that one not merely rewrite the AST, but also perform
167 elaborate manipulations on the types in order to convince the
168 type-checker that the resulting ASTs were permissible. And frankly,
169 it required substantial mental effort just to understand these
170 type manipulations, to say nothing of the work involved in
171 writing them in the first place. An optimization pass—correct
172 on paper and conceptually straightforward—would take an inordinate
173 amount of time and effort to write _and_ understand. The code
174 had become brittle, difficult to reason about, and most importantly,
175 difficult to change.
176
177 We ended up removing that extra type information, going back to
178 an AST with no type parameters.
179
180 Except we didn't remove guarantees: we just encoded them with other
181 kinds of checks. We still rule out invalid ASTs, we just don't do so by
182 _construction_. We can still verify that an optimization pass produces
183 correct code: we just run manually-written checks on the AST.
184
185 What's the summary here? Why did we ditch the stronger guarantees that
186 the previous edition provided us? Well, in short:
187
188 - The ergonomic advantages were gone. _Working with more static type
189 information took significantly more effort than working with the
190 more weakly-typed AST._
191 - The type system was _not amenable to the type information we wanted
192 to encode_. In this case, I suspect we would have been better suited
193 by a proper dependently typed language, as Haskell does not have the
194 necessary features for manipulating the proofs needed to convince the
195 type system of things.
196 - The constraints we wanted to express _were still otherwise expressible_
197 in ways that we could reason about: in fact, it was straightforward to
198 express them as simple manually-written passes. In the end, our program
199 _still checked the properties that needed to be checked_.
200
201 # How Does This Apply To Dynamic Typing?
202
203 After all, we didn't port our application from Haskell to Python. We
204 obviously were still reaping the benefits of Haskell's rather strong
205 static type system. So why am I citing this story in a blog post
206 about _dynamic_ types?
207
208 Well, look at the three criteria that led to us reducing the amount
209 of static type information. All three of these are also criteria that
210 might push you to choose a dynamic language for a particular problem
211 domain:
212
213 - The flip-side to Harper's argument that dynamic (or "unityped")
214 languages are in fact statically typed is that if you're writing a
215 program in which you will need to constantly check tags at runtime[^3], you
216 might as well be using a dynamic language. After all—it's what you'd
217 be doing in a static language, but much more concisely and
218 ergonomically, right? For some problem domains, _dynamic types
219 take less effort to write and understand than static types_.
220 - A program in a dynamic language generally has some kind of informal
221 type system applied by the programmer. Sometimes these systems can be
222 quite difficult to express even in sophisticated systems like dependently
223 typed languages[^1], and
224 equally often multiple type disciplines can be applied simultaneously[^2].
225 These _informal type disciplines can always be expressed in a dynamic
226 language, but may not be straightforwardly expressible in particular
227 static languages_.
228 - Dynamic languages can still express types: sometimes at runtime and
229 even sometimes (with the ability to write significantly powerful macros,
230 as in the Lisp family) at compile-time. _You can still check the
231 properties you want to check_.
232
233 Does this mean that you ought to run out and ditch your types? Of course
234 not! Static typing is a powerful tool, and dependent static typing even
235 more so. Dynamic languages are, after all, doing these checks at runtime,
236 and there is _great_ value in using a tool that can verify properties
237 statically. And for software that absolutely must be correct,
238
239 But there is also great value in understanding that a particular type system
240 may or may not be amenable to the properties you want it to express, and that
241 there are problem domains in which doing things in a dynamically typed
242 language is reasonable or even advisable. It might even be possible to express
243 the type discipline you're using in a static way, but not in an existing
244 statically typed language. In that case, you could write it informally and
245 gradually layer your type discipline onto the code in a checkable way.
246
247 Additionally, there are shades of static verification: great inroads have
248 been made in the areas of gradual typing, which allows a system to be
249 selectively dynamically and statically typed. Some systems would benefit
250 from this! Some would not.
251
252 The
253
254 # Appendices: A Few Specific Objections to Dynamic Types
255
256 ## Large Systems In Dynamically Typed Languages Are Unmaintainable or Unwritable
257
258 Proof: by construction. There are many, many large codebases in dynamically
259 typed languages which have varying degrees of maintainability. This is also
260 true of statically typed codebases.
261
262 ## The Curry-Howard Isomorphism
263
264
265
266 [^1]: As a simple example, various idioms allow you to construct new types
267 based on runtime values, as in Python's `namedtuple`. Generating new types
268 in a dynamically typed language is incredibly straightforward, but doing
269 the corresponding work in (say) Haskell requires a great deal of effort,
270 including elaborate macro facilities and metaprogramming.
271
272 [^2]: This is exactly [what Matthias Felleisen says in a comment on Bob
273 Harper's blog](http://existentialtype.wordpress.com/2014/04/21/bellman-confirms-a-suspicion/#comment-1362):
274
275 > The hidden blessing is that DT programmers can import, super-impose,
276 > and integrate many different static type disciplines — informally — and
277 > use them to design their code and to reason about it. The result are
278 > programming idioms that no one ordinary static type system can
279 > uniformly check, and that is why DT programmers cannot accept the
280 > phrase “DT languages are statically typed.”
281
282 Note the similarity to how [Alan Key describes his original conception of
283 objects](http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/doc_kay_oop_en):
284
285 > My math background made me realize that each object could have
286 > several algebras associated with it, and there could be families of
287 > these, and that these would be very very useful. The term
288 > "polymorphism" was imposed much later (I think by Peter Wegner) and
289 > it isn't quite valid, since it really comes from the nomenclature of
290 > functions, and I wanted quite a bit more than functions. I made up a
291 > term "genericity" for dealing with generic behaviors in a
292 > quasi-algebraic form.
293
294 [^3]: One situation in which this is reasonable is in long-running or scripting
295 languages. Erlang is a good example: you're probably gonna receive a lot of
296 incorrect messages, so instead of a static type system in which you constantly
297 branch on various tags, it provides a dynamic system. Similarly, success with
298 statically typed operating system shells has been limited.
299
300
301 # SNIPPETS
302
303 ## But What About Curry-Howard?
304
305 The Curry-Howard Isomorphism is about the relationship between programs and
306 proofs in a logic. It is very cool. It has _almost nothing_ to do with
307 whether or not dynamic languages are worthwhile.
308
309 Firstly: the fact that programs correspond to a logic is a really strong
310 advantage for two reasons:
311
312 1. It gives you the ability to reason about programming languages themselves
313 using the tools of mathematics.
314 2. It has important ramifications for the construction of dependently-typed
315 languages, in which values and logic intermingle.
316
317 Notice that this doesn't say much about the programs written in a language
318 like Haskell: that's because the Curry-Howard Isomorphism has nothing to
319 say about whether they're "more correct" than programs written in another
320 language. In a language that's not dependently typed, the fact that
321 pairs correspond to conjunction doesn't have any bearing on their use.
322
323 You might argue that you get nice properties like
324
325 ~~~~
326 p: A * B p: A * B
327 ---------- ----------
328 fst p: A snd p: B
329 -----------------------
330 (fst p, snd p): A * B
331 ~~~~
332
333 out of the logic—but Scheme has the exact same law
334
335 ~~~~
336 (pair? p) => p == (cons (car p) (cdr p))
337 ~~~~
338
339 and has no type system to correspond to any logic!
340
341 As an aside: the Curry-Howard Isomorphism is often perceived to be
342 more magical than it is. People act as though it's some kind of
343 peek into the deep structure of reality, but (and this is a
344 bigger point) all mathematics is fundamentally a human invention.
345 A very useful and elegant one! But still the product of people.[^people]
346 Programming languages like the lambda-calculus came directly out of
347 the field of logic, and were invented by people steeped in the same
348 intellectual tradition: seeing the correspondance between the
349 lambda-calculus and logic as evidence that logic is an "ontological
350 ultimate" is like seeing the correspondance between French and
351 Spanish as evidence that Romance languages are inscribed in the
352 structure of the universe.
353
354 [^people]: One of the most disappointing things about the widespread
355 belief that math is some kind of deep universal truth is that people
356 tend to ignore the human factors such as culture and creativity that
357 go into creating mathematical formalisms.
1 {- Some example programs in the Matzo language
2 -
3 - It is important to note that, in the Matzo language, the value of a
4 - variable is generally not a single value, but one of a set of values.
5 - Every time a variable is used, an element is drawn from this set;
6 - so the following program can print 1, 2, 3, 10, 20, or 30. -}
7
8 x = 1 | 2 | 3
9 f = \ n -> n
10 | \ n -> n * 10
11 puts f.x;
12
13
14 {- The Aquan language program -}
15
16 {- The ::= operator is for the common case that we want to select from a
17 - whole bunch of literal characters or words. It is referred to as the
18 - 'literal assignment operator'. -}
19 vowel ::= a e i o u
20 consonant ::= p t k w h m n l
21
22 {- The := operator sets the value on the left to be
23 syll = vowel | consonant vowel
24 | vowel "'" | consonant vowel "'"
25 word = syll syll (6 @ syll)
26
27
28 {- Fixing a value -}
29
30 {- Identifiers prefixed by 'fixed' stay the same: -}
31
32 n = 1 | 2
33 fixed m = n
34 {- The following could print either "1,2", "2,1" "1,1" or "2,2" -}
35 puts "{n},{n}"
36 {- The following can only print "1,1" or "2,2" -}
37 puts "{m},{m}"
38
39
40 {- A random person description -}
41
42 {- Identifiers that begin with capitals are symbols. Symbols can be
43 - compared and matched against. It is not allowed to use an identifier
44 - that begins with a capital as a variable. -}
45
46 pronoun.Male = "he"
47 pronoun.Female = "she"
48 person.Male = "man"
49 person.Female = "woman"
50
51 fixed gender = Male | Female
52 hair ::= long short
53 hair_color ::= brown black blonde red white
54 eye_color ::= blue black brown green
55 mood ::= happy sad angry
56
57 puts "This {person.gender} has {hair}, {hair_color} hair and "
58 "{eye_color} eyes; {pronoun.gender} appears to be "
59 "quite {mood}."
1 <html>
2 <head>
3 <style type="text/css">
4 body {
5 font-family: "Arial", "Helvetica", sans-ser<span class="keyword">if</span>;
6 background-color: #ffffff;
7 color: #111111;
8 }
9
10 h2 {
11 text-align: center;
12 }
13
14 .main {
15 background-color: #eeeeee;
16 width: 800px;
17 margin-left: auto;
18 margin-right: auto;
19 padding-top: 10px;
20 padding-bottom: 20px;
21 padding-left: 40px;
22 padding-right: 40px;
23 }
24
25 .keyword { color: #336699; }
26 .constr { color: #993366; }
27 .string { color: #339966; }
28 .comment { color: #666666; }
29 </style>
30 </head>
31 <body>
32 <div class="main">
33 <pre>
34 <span class="comment">(** Some example programs in the Matzo language
35 *
36 * It is important to note that, in the Matzo language, the value of a
37 * variable is generally not a single value, but one of a set of values.
38 * Every time a variable is used, an element is drawn from this set;
39 * so the following program can print 1, 2, 3, 10, 20, or 30. *)</span>
40
41 x := 1 | 2 | 3;
42 f := { n => x }
43 | { n => n * 10 };
44 <span class="keyword">puts</span> f(x);
45
46
47 <span class="comment">(** The Aquan language program *)</span>
48
49 <span class="comment">(* The ::= operator is for the common case that we want to select from a
50 * whole bunch of literal characters or words. It is referred to as the
51 * 'literal assignment operator'. *)</span>
52 vowel ::= <span class="string">a e i o u</span>;
53 consonant ::= <span class="string">p t k w h m n l</span>;
54
55 <span class="comment">(* Normally, each alternative has equal weight, but explicit weights can be
56 * given; weights which are not given are considered to be 1. *)</span>
57 syll := 4: vowel | 4: consonant vowel
58 | vowel <span class="string">"'"</span> | consonant vowel <span class="string">"'"</span>;
59
60 <span class="comment">(* The `n @ value` syntax represents 1 to n possible repetitions of value.
61 * e.g. 3 @ ("a" | "b") is the same as
62 * ( ("a" | "b")
63 * | ("a" | "b") ("a" | "b")
64 * | ("a" | "b") ("a" | "b") ("a" | "b") )
65 * and is given special syntax because it is a common case in word generation.
66 * Below, it means that every word will have at least three and at most eight
67 * syllables. *)</span>
68 word := syll syll (6 @ syll);
69 <span class="keyword">puts</span> word;
70
71
72 <span class="comment">(** Fixing a value *)</span>
73
74 <span class="comment">(* The built-in pseudo-function `choose` allows you to force a value
75 * to stay the same. *)</span>
76
77 n := 1 | 2;
78 m := choose(n);
79 <span class="comment">(* The following could print either "1,2", "2,1" "1,1" or "2,2" *)</span>
80 <span class="keyword">puts</span> <span class="string">"{n},{n}"</span>
81 <span class="comment">(* The following can only print "1,1" or "2,2" *)</span>
82 <span class="keyword">puts</span> <span class="string">"{m},{m}"</span>
83
84
85 <span class="comment">(** A random person description *)</span>
86
87 <span class="comment">(* Identifiers that begin with capitals are symbols. Symbols can be
88 * compared and matched against. It is not allowed to use an identifier
89 * that begins with a capital as a variable. *)</span>
90
91 pronoun :=
92 { <span class="constr">Male</span> => <span class="string">"he"</span>
93 | <span class="constr">Female</span> => <span class="string">"she"</span>
94 };
95 person :=
96 { <span class="constr">Male</span> => <span class="string">"man"</span>
97 | <span class="constr">Female</span> => <span class="string">"woman"</span>
98 };
99
100 gender := choose(<span class="constr">Male</span> | <span class="constr">Female</span>);
101 hair ::= <span class="string">long short</span>;
102 hair_color ::= <span class="string">brown black blonde red white</span>;
103 eye_color ::= <span class="string">blue black brown green</span>;
104 mood ::= <span class="string">happy sad angry</span>;
105
106 <span class="keyword">puts</span> <span class="string">"This {person(gender)} has {hair}, {hair_color} hair and "
107 "{eye_color} eyes; {pronoun(gender)} appears to be "
108 "quite {mood}."</span>;
109 </pre>
110 </div>
111 </body>
112 </html>
1 -- Some example programs in the Matzo language
2 --
3 -- It is important to note that, in the Matzo language, the value of a
4 -- variable is generally not a single value, but one of a set of values.
5 -- Every time a variable is used, an element is drawn from this set;
6 -- so the following program can print 1, 2, 3, 10, 20, or 30.
7
8 x = 1 | 2 | 3
9 f = (\ n . x )
10 | (\ n . n * 10 )
11 puts f.x
12
13
14 -- The Aquan language program
15
16 {- The ::= operator is for the common case that we want to select from a
17 - whole bunch of literal characters or words. It is referred to as the
18 - 'literal assignment operator'. -}
19 vowel ::= a e i o u
20 consonant ::= p t k w h m n l
21
22 {- The := operator sets the value on the left to be
23 syll = vowel
24 | consonant vowel
25 | vowel "'"
26 | consonant vowel "'"
27 word = syll syll (6 @ syll)
28
29
30 {- Fixing a value -}
31
32 {- The built-in pseudo-function `choose` allows you to force a value
33 * to stay the same. -}
34
35 n = 1 | 2
36 fixed m = n
37 {- The following could print either "1,2", "2,1" "1,1" or "2,2" -}
38 puts "{n},{n}"
39 {- The following can only print "1,1" or "2,2" -}
40 puts "{m},{m}"
41
42
43 {- A random person description -}
44
45 {- Identifiers that begin with capitals are symbols. Symbols can be
46 - compared and matched against. It is not allowed to use an identifier
47 - that begins with a capital as a variable. -}
48
49 pronoun Male = "he"
50 pronoun Female = "she"
51 person Male = "man"
52 person Female = "woman"
53
54 fixed gender = Male | Female
55 hair ::= long short
56 hair_color ::= brown black blonde red white
57 eye_color ::= blue black brown green
58 mood ::= happy sad angry
59
60 puts "This {person.gender} has {hair}, {hair_color} hair and "
61 "{eye_color} eyes; {pronoun.gender} appears to be "
62 "quite {mood}."
1 (** Some example programs in the Matzo language
2 *
3 * It is important to note that, in the Matzo language, the value of a
4 * variable is generally not a single value, but one of a set of values.
5 * Every time a variable is used, an element is drawn from this set;
6 * so the following program can print 1, 2, 3, 10, 20, or 30. *)
7
8 x := 1 | 2 | 3;
9 f := { n => x }
10 | { n => n * 10 };
11 puts f(x);
12
13
14 (* The Aquan language program *)
15
16 (* The ::= operator is for the common case that we want to select from a
17 * whole bunch of literal characters or words. It is referred to as the
18 * 'literal assignment operator'. *)
19 vowel ::= a e i o u;
20 consonant ::= p t k w h m n l;
21
22 (* The := operator sets the value on the left to be
23 syll := vowel | consonant vowel
24 | vowel "'" | consonant vowel "'";
25 word := syll syll (6 @ syll);
26
27
28 (* Fixing a value *)
29
30 (* The built-in pseudo-function `choose` allows you to force a value
31 * to stay the same. *)
32
33 n := 1 | 2;
34 m := n; fix m;
35 (* The following could print either "1,2", "2,1" "1,1" or "2,2" *)
36 puts "{n},{n}"
37 (* The following can only print "1,1" or "2,2" *)
38 puts "{m},{m}"
39
40
41 (* A random person description *)
42
43 (* Identifiers that begin with capitals are symbols. Symbols can be
44 * compared and matched against. It is not allowed to use an identifier
45 * that begins with a capital as a variable. *)
46
47 pronoun :=
48 { Male => "he"
49 ; Female => "she"
50 };
51 person :=
52 { Male => "man"
53 ; Female => "woman"
54 };
55
56 gender := Male | Female;
57 fix gender;
58 hair ::= long short;
59 hair_color ::= brown black blonde red white;
60 eye_color ::= blue black brown green;
61 mood ::= happy sad angry;
62
63 puts "This {person.gender} has {hair}, {hair_color} hair and "
64 "{eye_color} eyes; {pronoun.gender} appears to be "
65 "quite {mood}.";
66
67
1 V ::= [C] | 0 | V + V' | 1 | V * V'
2 C ::= {V} | V -> C | top | C & C'
3
4 neg : [ int -> {int} ]
5 add : [ int -> int -> {int} ]
6
7 abs : [ int -> {int} ]
8 abs = [ \ n: int.
9 if n < 0
10 then {0 - n}
11 else {n}
12 ]
13
14 add : [ int -> {int} ]
15 add = [ \ (x: int) (y: int) . { x + y } ]
16
17 data List a where
18 Nil : List a
19 Cons : a -o List a -o List a
20
21 nums : List Int
22 nums = Cons(1, Cons(2, Cons(3, Nil)))
23
24 -- fn name arg1 arg2 ... argn = blah
25 -- ==>
26 -- name = [ \ arg1 arg2 ... argn . blah ]
27
28 sumlist : [ List Int -> {Int} ]
29 fn sumlist (Cons x xs) = let y <- sumlist xs in { x + y }
30 sumlist Nil = { 0 }
31
32 data Tree where
33 Leaf : Int -o Tree
34 Node : Tree * Tree -o Tree
35
36 codata Stream a where
37 .head : {a}
38 .tail : Stream
39
40 ones : Stream Int
41 ones = rec this
42 this.head <- [1]
43 this.tail <- this
1 # Zippered Tree
2
3 data Path where
4 Root : () -> Path
5 Left : Path -> Path
6 Right : Path -> Path
7
8 codata Tree : Path -> * -> * where
9 node : Tree p a -> a
10 left : Tree p a -> Tree (Left p) a
11 right : Tree p a -> Tree (Right p) a
12 unLeft : Tree (Left p) a -> Tree p a
13 unRight : Tree (Right p) a -> Tree p a
14
15 ones : Tree Root Int
16 ones =
17 merge e from
18 node e <- 1
19 left e <- l e
20 right e <- r e
21 where
22 l t = merge e from
23 node e <- 1
24 left e <- l e
25 right e <- r e
26 unLeft e <- t
27 r t = merge e from
28 node e <- 1
29 left e <- l e
30 right e <- r e
31 unRight e <- t
32
33 # Typed Forth
34
35 codata Forth s where
36 push : Forth b -> a -> Forth (a, b)
37 drop : Forth (a, b) -> Forth b
38 app : Forth (a, (b, c)) -> (a -> b -> d) -> Forth (d, c)
39 get : Forth (a, b) -> a
40
41 prog1 : Int
42 prog1 = empty
43 .push 5
44 .push 7
45 .app (+)
46 .get
47
48 empty : Forth ()
49 empty = merge e from
50 push e <- push0
51
52 push0 : a -> Forth (a, ())
53 push0 x = merge e from
54 push e <- push1 e
55 drop e <- empty
56 get e <- x
57
58 push1 : Forth (b, c) -> a -> Forth (a, (b, c))
59 push1 f x = merge e from
60 push e <- push1 e
61 drop e <- f
62 get e <- x
63 app e <- doApp
64 where doApp f =
1 I'm still working on the Matzo language, and I need to finish the parser (as
2 I can now evaluate more programs than I can parse, and have code written to
3 an as-yet unimplemented module spec.) Here are a few features I'm
4 _considering_ but haven't implemented yet, and how they might interact:
5
6 ## Dynamic Default Variables
7
8 This opens a small can of worms, so to be clear, dynamic variables will
9 reside in a different namespace than normal variables. I don't know yet
10 whether said namespace will be denoted with a sigil (say `#x`) or whether
11 you might have to pass a symbol to a function (like `get.X`) to access
12 their values. The idea is that certain functions will have sensible
13 defaults that one might want to override, and rather than explicitly
14 matching on arity, one can instead pass them in dynamically using a
15 particular syntax. In the examples below, I'll use an explicit syntax for
16 lookup of a dynamically scoped variable; in particular, `get.x.y` will
17 look up a dynamically scoped variable `x` (typically a symbol), and if it
18 hasn't been supplied, will use the default value `y`.
19
20 foo :=
21 let combine := get.Combine.cat in
22 combine."a"."b"
23 bar := foo with Combine := (\ x y . x ";" y)
24
25 puts foo
26 (* prints "ab", as it uses the default combiner `cat` *)
27 puts bar
28 (* prints "a;b" *)
29
30 This will continue down dynamically, so
31
32 s1 := (get.Pn."e") " stares intently."
33 s2 := "It is clear that " (get.Pn."e")
34 " is interested in what is happening."
35 sent := se.<s1,s2>
36 puts sent;
37 puts sent with fixed Pn := "he" | "she" | "e"
38
39 will force `Pn` to be the chosen value in all subexpressions evaluated
40 underneath the `with` clause. (Notice that nondeterminism still
41 works in dynamically computed variables, so one must declare them as
42 fixed at the binding site if you want them to be computed exactly
43 once.)
44
45 ## Text Combinators
46
47 I've used one of these above: right now, I have a few planned.
48
49 puts wd.<"a","b","c">
50 (* prints "abc" *)
51
52 puts nm.<"a","b","c">
53 (* prints "Abc" *)
54
55 puts se.<"a","b","c">
56 (* prints "A b c" *)
57
58 puts pa.<"a","b","c">
59 (* prints "A. B. C." *)
60
61 So effectively, they function as ways of combining strings in a more
62 intelligent way. I plan for them to do some analysis of the strings so that
63 they don't, say, produce extraneous spaces or punctuation. (The names are
64 of course short for `word`, `name`, `sentence`, and `paragraph`, respectively,
65 and may very well be alises for those longer names.)
66
67 ## Rebindable Syntax
68
69 The conjunction of the previous two suggests that certain bits of syntax
70 should be rebindable. A prominent example is concatenation, e.g.
71
72 puts "foo" "bar" "baz"
73 (* prints "foobarbaz" *)
74
75 puts "foo" "bar" "baz" with Cat := se
76 (* prints "Foo bar baz.", as normal string concatenation
77 * has been overloaded by `se` *)
78
79 puts "foo" "bar" "baz" with Cat := fold.(\ x y -> x ";" y)
80 (* prints "foo;bar;baz", as normal string concatenation
81 * has been overloaded by a custom function *)
82
83 This could lead to some pretty phenomenal weirdness, though:
84
85 f := \ x y -> add.x.y
86 puts f.<1,2> where App := \ f x . fold.(\ g y . g.y).(append.f.x)
87 (* prints 3, as we have overloaded function application to
88 * automatically uncurry functions *)
89
90 ...so maybe there should be a limit on it.
91
92 ## Error Handling
93
94 Still not sure on this one. I don't particularly want to bring monads
95 into this, mostly because I want the language to be a DSL for strings
96 and not a general-purpose programming language, but at the same time,
97 it might be nice to have a simple exception-like mechanism. One idea
98 I was playing with was to implement a backtracking system so that
99 errors (both raised by users and by built-in problems) could simply
100 resume at particular points and retry until some retry limit is reached.
101 For example, you could reject certain unlikely combinations:
102
103 x ::= a b c d
104 wd := let result := x x x x
105 in if eq.result."aaaa" then raise Retry else result
106 puts mark Retry in wd
107
108 Here, `mark exp in wd` corresponds roughly to the following imperative
109 pseudocode:
110
111 { retry_count := 0
112 ; while (retry_count < retry_max)
113 { try { return wd; }
114 catch (exp) { retry_count ++; }
115 }
116 }
117
118 It's a much more limited form of exception handling, which may or may not
119 be desirable, but does give you some ability to recover from errors so long
120 as at least _some_ execution of your program will be error-less.
121
122 All this is heavily open to change, so we'll see.
1 #!/usr/bin/jroute
2
3 # for a quote
4 { "title" : "Quotes"
5 , "usejs" : true
6 , "copy" : "all quotes used under fair use &c &c"
7 , "onload" : "\"doLoadHighlight()\""
8 , "contents" :
9 { "quotelist" : [ { "content" : `(invoke "markdown" content)
10 , ?("author" : `(invoke "markdown" author))
11 }
12 | { "content": c, ?("author": a) } <- @(arg 1)
13 ]
14 , "focus" : `(arg 0)
15 }
16 }
17
18 # for a work
19 let
1 % Latka: A Language For Random Text Generation
2 % Getty D. Ritter
3
4 Latka is a total, strongly typed functional programming language for
5 generating random text according to predefined patterns. To this end, Latka
6 incorporates weighted random choice as an effect in its expression language
7 and provides a set of combinators for constructing high-level semantic
8 constructs such as sentences and paragraphs. The eventual purpose of Latka
9 is to produce code that can be embedded into other systems such as video
10 games.
11
12 The primary operators of the expression language are concatenation (which is
13 represented by juxtaposing two expressions, e.g., `e1 e2`) and choice
14 (which is represented by a vertical bar to mimic BNF notation, e.g.,
15 `e1 | e2`), with weighted choice `n: e1 | m: e2` (where `m` and `n`
16 are `Nat`s) being a convenient syntactic sugar. Another piece of syntactic
17 sugar is repetition, in which `n @ e` (where `n` is an expression of type `Nat`)
18 stands for `n` repetitions of `e`. A simple program can be
19 built out of just these primitives:
20
21 consonant, vowel, syllable : String
22 consonant = "p" | "t" | "k" | "w"
23 | "h" | "m" | "n"
24 vowel = "a" | "e" | "i" | "o" | "u"
25 syllable = let v = 5: vowel | vowel "'" in
26 5: consonant v | 2: v
27
28 -- e.g., pate'hai, aku, e'epoto'
29 puts (2 | 3 | 4 | 5) @ syllable
30
31 By default, evaluation order is call-by-name so that repeated use of the same
32 name will result in different values, but by prefixing any binding with the
33 keyword `fixed` one can specify call-by-value evaluation order, effectively
34 selecting a value consistently for the duration of the use of the name:
35
36 -- Can evaluate to aa, ab, ba, or bb
37 puts let x = "a" | "b" in x x
38
39 -- Can only ever evaluate to aa or bb
40 puts let fixed x = "a" | "b" in x x
41
42 Latka has a set of features like other strongly typed functional
43 languages like Haskell or OCaml, including named sums as a datatype mechanism,
44 pattern matching, and typeclasses for ad-hoc polymorphism.
45 Like Coq or Agda, recursive functions are
46 restricted to structural recursion to ensure termination of embedded
47 Latka programs.
48 Latka also includes a set of functions for structuring larger blocks of text.
49 These take advantage of a particular aspect of the type system which allows
50 for variadic polymorphism. In the example below, `sent` is one such function;
51 it takes arbitrary-length tuples of values coercible to sentence fragments
52 and intelligently converts them to a correct sentence with capitalization
53 and punctuation.
54
55 samp : String * Word * Sentence
56 samp = ("one",wd."two",sent.("three","four"))
57
58 -- prints "One two three four."
59 puts sent.samp
60
61 Some of these examples can be seen in practice in the following program.
62
63 import Language.Natural.Latin as Latin
64
65 data Gender = Male | Female
66
67 pronoun : Gender -> Word
68 pronoun Male = wd."he"
69 pronoun Female = wd."she"
70
71 noun : Gender -> Word
72 noun Male = wd."man"
73 noun Female = wd."woman"
74
75 puts let fixed g = Male | Female | Other in
76 para.( sent.( "You see a Roman"
77 , noun.g
78 , "from"
79 , proper_noun.(Latin/cityName)
80 )
81 , sent.( pronoun.g
82 , "has"
83 , ("brown" | "black" | "blonde")
84 , "hair and carries"
85 , range.50.500
86 , "denarii"
87 )
88 )
89 -- It might print, for example, "You see a
90 -- Roman woman from Arucapa. She has black
91 -- hair and carries 433 denarii."
1 \documentclass[twocolumn]{article}
2 \usepackage[T1]{fontenc}
3 \usepackage{lmodern}
4 \usepackage[cm]{fullpage}
5 \renewcommand{\ttdefault}{pcr}
6 \usepackage[usennames,dvipsnames]{xcolor}
7 \usepackage{listings}
8 \lstdefinelanguage{latka}
9 { morekeywords={puts,import,data,let,in,as,fixed}
10 , sensitive=false
11 , morecomment=[l]{--}
12 , morestring=[b]"
13 }
14 \lstset{language=latka}
15 \lstdefinestyle{lkstyle}
16 { basicstyle=\ttfamily\small
17 , commentstyle=\itshape\color{black!60}
18 % , keywordstyle=\bfseries\color{MidnightBlue}
19 , keywordstyle=\bfseries
20 % , stringstyle=\color{ForestGreen}
21 , stringstyle=\color{black!65}
22 , showstringspaces=false
23 }
24 \lstset{style=lkstyle}
25 \usepackage{amssymb,amsmath}
26 \usepackage{ifxetex,ifluatex}
27 \usepackage{fixltx2e} % provides \textsubscript
28 % use upquote if available, for straight quotes in lstlisting environments
29 \IfFileExists{upquote.sty}{\usepackage{upquote}}{}
30 \ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex
31 \usepackage[utf8]{inputenc}
32 \else % if luatex or xelatex
33 \usepackage{fontspec}
34 \ifxetex
35 \usepackage{xltxtra,xunicode}
36 \fi
37 \defaultfontfeatures{Mapping=tex-text,Scale=MatchLowercase}
38 \newcommand{\euro}{€}
39 \fi
40 % use microtype if available
41 \IfFileExists{microtype.sty}{\usepackage{microtype}}{}
42 \ifxetex
43 \usepackage[setpagesize=false, % page size defined by xetex
44 unicode=false, % unicode breaks when used with xetex
45 xetex]{hyperref}
46 \else
47 \usepackage[unicode=true]{hyperref}
48 \fi
49 \hypersetup{breaklinks=true,
50 bookmarks=true,
51 pdfauthor={Getty D. Ritter},
52 pdftitle={Latka: A Language For Random Text Generation},
53 colorlinks=true,
54 urlcolor=blue,
55 linkcolor=magenta,
56 pdfborder={0 0 0}}
57 \urlstyle{same} % don't use monospace font for urls
58 \setlength{\parindent}{0pt}
59 \setlength{\parskip}{6pt plus 2pt minus 1pt}
60 \setlength{\emergencystretch}{3em} % prevent overfull lines
61 \setcounter{secnumdepth}{0}
62
63 \title{Latka: A Language For Random Text Generation}
64 \author{Getty D. Ritter\\
65 Galois, Inc.\\
66 \texttt{\small gdritter@galois.com}}
67 \date{}
68
69 \begin{document}
70 \maketitle
71
72 Latka is a total, strongly typed functional programming language for
73 generating random text according to predefined patterns. To this end,
74 Latka incorporates weighted random choice as an effect in its expression
75 language and provides a set of combinators for constructing high-level
76 semantic constructs such as sentences and paragraphs. The eventual
77 purpose of Latka is to produce code that can be embedded into other
78 systems such as video games.
79
80 The primary operators of the expression language are concatenation
81 (which is represented by juxtaposing two expressions, e.g.,
82 \texttt{e1 e2}) and choice (which is represented by a vertical bar to
83 mimic BNF notation, e.g., \texttt{e1 \textbar{} e2}), with weighted
84 choice \texttt{m: e1 \textbar{} n: e2} (where \texttt{m} and \texttt{n}
85 are \texttt{Nat}s) being a convenient syntactic sugar. Another piece of
86 syntactic sugar is repetition, in which \texttt{n @ e} (where \texttt{n}
87 is an expression of type \texttt{Nat}) stands for \texttt{n} repetitions
88 of \texttt{e}. A simple program can be built out of just these
89 primitives:
90
91 \begin{lstlisting}
92 consonant, vowel, syllable : String
93 consonant = "p" | "t" | "k" | "w"
94 | "h" | "m" | "n"
95 vowel = "a" | "e" | "i" | "o" | "u"
96 syllable = let v = 5: vowel | vowel "'" in
97 5: consonant v | 2: v
98
99 -- e.g., pate'hai, aku, e'epoto'
100 puts (2 | 3 | 4 | 5) @ syllable
101 \end{lstlisting}
102
103 By default, evaluation order is call-by-name so that repeated use of the
104 same name will result in different values, but by prefixing any binding
105 with the keyword \lstinline!fixed! one can specify call-by-value evaluation
106 order, effectively selecting a value consistently for the duration of
107 the use of the name:
108
109 \begin{lstlisting}
110 -- Can evaluate to aa, ab, ba, or bb
111 puts let x = "a" | "b" in x x
112
113 -- Can only ever evaluate to aa or bb
114 puts let fixed x = "a" | "b" in x x
115 \end{lstlisting}
116
117 Latka has a set of features like other strongly typed functional
118 languages like Haskell or OCaml, including named sums as a datatype
119 mechanism, pattern matching, and typeclasses for ad-hoc polymorphism.
120 Like Coq or Agda, recursive functions are restricted to structural
121 recursion to ensure termination of embedded Latka programs. Latka also
122 includes a set of functions for structuring larger blocks of text. These
123 take advantage of a particular aspect of the type system which allows
124 for variadic polymorphism. In the example below,
125 \texttt{sent} is one
126 such function; it takes arbitrary-length tuples of values coercible to
127 sentence fragments and intelligently converts them to a correct sentence
128 with capitalization and punctuation.\footnote{Latka's function
129 invocation syntax is an infixed \texttt{.} operator, borrowed from the
130 notation Edsger W. Dijkstra outlined in
131 \textit{The notational conventions I adopted, and why} (2000). This operator has
132 the highest binding power and is left-associative so that
133 \texttt{f.x.y == (f.x).y}.}
134
135 \begin{lstlisting}
136 samp : String * Word * Sentence
137 samp = ("one",wd."two",sent.("three","four"))
138
139 -- prints "One two three four."
140 puts sent.samp
141 \end{lstlisting}
142
143 Some of these examples can be seen in practice in the following program.
144
145 \begin{lstlisting}
146 import Language.Natural.Latin as Latin
147
148 data Gender = Male | Female
149
150 pronoun : Gender -> Word
151 pronoun . Male = wd."he"
152 pronoun . Female = wd."she"
153
154 noun : Gender -> Word
155 noun . Male = wd."man"
156 noun . Female = wd."woman"
157
158 puts let fixed g = Male | Female in
159 para.( sent.( "You see a Roman"
160 , noun.g
161 , "from"
162 , proper_noun.(Latin/cityName)
163 )
164 , sent.( pronoun.g
165 , "has"
166 , ("brown"|"black"|"blonde")
167 , "hair and carries"
168 , range.50.500
169 , "denarii"
170 )
171 )
172 -- It might print, for example, "You see a
173 -- Roman woman from Arucapa. She has black
174 -- hair and carries 433 denarii."
175 \end{lstlisting}
176
177 \end{document}
1 # Latka
2 ## Grammar
3
4 <COMMAND> := put <EXPR> |
Binary diff not shown
1 \documentclass[twocolumn]{article}
2 \usepackage[T1]{fontenc}
3 \usepackage{lmodern}
4 \usepackage[cm]{fullpage}
5 \usepackage[usennames,dvipsnames]{xcolor}
6 \usepackage{listings}
7 \lstdefinelanguage{latka}
8 { morekeywords={puts,import,data,let,in,as,fixed}
9 , sensitive=false
10 , morecomment=[l]{--}
11 , morestring=[b]"
12 }
13 \lstset{language=latka}
14 \lstdefinestyle{lkstyle}
15 { basicstyle=\ttfamily\small
16 , commentstyle=\color{Gray}
17 , keywordstyle=\color{MidnightBlue}
18 , stringstyle=\color{ForestGreen}
19 , showstringspaces=false
20 }
21 \lstset{style=lkstyle}
22 \usepackage{amssymb,amsmath}
23 \usepackage{ifxetex,ifluatex}
24 \usepackage{fixltx2e} % provides \textsubscript
25 % use upquote if available, for straight quotes in lstlisting environments
26 \IfFileExists{upquote.sty}{\usepackage{upquote}}{}
27 \ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex
28 \usepackage[utf8]{inputenc}
29 \else % if luatex or xelatex
30 \usepackage{fontspec}
31 \ifxetex
32 \usepackage{xltxtra,xunicode}
33 \fi
34 \defaultfontfeatures{Mapping=tex-text,Scale=MatchLowercase}
35 \newcommand{\euro}{€}
36 \fi
37 % use microtype if available
38 \IfFileExists{microtype.sty}{\usepackage{microtype}}{}
39 \ifxetex
40 \usepackage[setpagesize=false, % page size defined by xetex
41 unicode=false, % unicode breaks when used with xetex
42 xetex]{hyperref}
43 \else
44 \usepackage[unicode=true]{hyperref}
45 \fi
46 \hypersetup{breaklinks=true,
47 bookmarks=true,
48 pdfauthor={Getty D. Ritter},
49 pdftitle={Latka: A Language For Random Text Generation},
50 colorlinks=true,
51 urlcolor=blue,
52 linkcolor=magenta,
53 pdfborder={0 0 0}}
54 \urlstyle{same} % don't use monospace font for urls
55 \setlength{\parindent}{0pt}
56 \setlength{\parskip}{6pt plus 2pt minus 1pt}
57 \setlength{\emergencystretch}{3em} % prevent overfull lines
58 \setcounter{secnumdepth}{0}
59
60 \title{Latka: A Language For Random Text Generation}
61 \author{Getty D. Ritter}
62 \date{}
63
64 \begin{document}
65 \maketitle
66
67 Latka is a total, strongly typed functional programming language for
68 generating random text according to predefined patterns. To this end,
69 Latka incorporates weighted random choice as an effect in its expression
70 language and provides a set of combinators for constructing high-level
71 semantic constructs such as sentences and paragraphs. The eventual
72 purpose of Latka is to produce code that can be embedded into other
73 systems such as video games.
74
75 The primary operators of the expression language are concatenation
76 (which is represented by juxtaposing two expressions, e.g.,
77 \texttt{e1 e2}) and choice (which is represented by a vertical bar to
78 mimic BNF notation, e.g., \texttt{e1 \textbar{} e2}), with weighted
79 choice \texttt{n: e1 \textbar{} m: e2} (where \texttt{m} and \texttt{n}
80 are \texttt{Nat}s) being a convenient syntactic sugar. Another piece of
81 syntactic sugar is repetition, in which \texttt{n @ e} (where \texttt{n}
82 is an expression of type \texttt{Nat}) stands for \texttt{n} repetitions
83 of \texttt{e}. A simple program can be built out of just these
84 primitives:
85
86 \begin{lstlisting}
87 consonant, vowel, syllable : String
88 consonant = "p" | "t" | "k" | "w"
89 | "h" | "m" | "n"
90 vowel = "a" | "e" | "i" | "o" | "u"
91 syllable = let v = 5: vowel | vowel "'" in
92 5: consonant v | 2: v
93
94 -- e.g., pate'hai, aku, e'epoto'
95 puts (2 | 3 | 4 | 5) @ syllable
96 \end{lstlisting}
97
98 By default, evaluation order is call-by-name so that repeated use of the
99 same name will result in different values, but by prefixing any binding
100 with the keyword \texttt{fixed} one can specify call-by-value evaluation
101 order, effectively selecting a value consistently for the duration of
102 the use of the name:
103
104 \begin{lstlisting}
105 -- Can evaluate to aa, ab, ba, or bb
106 puts let x = "a" | "b" in x x
107
108 -- Can only ever evaluate to aa or bb
109 puts let fixed x = "a" | "b" in x x
110 \end{lstlisting}
111
112 Latka has a set of features like other strongly typed functional
113 languages like Haskell or OCaml, including named sums as a datatype
114 mechanism, pattern matching, and typeclasses for ad-hoc polymorphism.
115 Like Coq or Agda, recursive functions are restricted to structural
116 recursion to ensure termination of embedded Latka programs. Latka also
117 includes a set of functions for structuring larger blocks of text. These
118 take advantage of a particular aspect of the type system which allows
119 for variadic polymorphism. In the example below,
120 \texttt{sent} is one
121 such function; it takes arbitrary-length tuples of values coercible to
122 sentence fragments and intelligently converts them to a correct sentence
123 with capitalization and punctuation.\footnote{Latka's function
124 invocation syntax is an infixed \texttt{.} operator, borrowed from the
125 notation Edsger W. Dijkstra outlined in his note EWD1300. This operator has
126 the highest binding power and is left-associative so that
127 \texttt{f.x.y == (f.x).y}.}
128
129 \begin{lstlisting}
130 samp : String * Word * Sentence
131 samp = ("one",wd."two",sent.("three","four"))
132
133 -- prints "One two three four."
134 puts sent.samp
135 \end{lstlisting}
136
137 Some of these examples can be seen in practice in the following program.
138
139 \begin{lstlisting}
140 import Language.Natural.Latin as Latin
141
142 data Gender = Male | Female
143
144 pronoun : Gender -> Word
145 pronoun . Male = wd."he"
146 pronoun . Female = wd."she"
147
148 noun : Gender -> Word
149 noun . Male = wd."man"
150 noun . Female = wd."woman"
151
152 puts let fixed g = Male | Female in
153 para.( sent.( "You see a Roman"
154 , noun.g
155 , "from"
156 , proper_noun.(Latin/cityName)
157 )
158 , sent.( pronoun.g
159 , "has"
160 , ("brown"|"black"|"blonde")
161 , "hair and carries"
162 , range.50.500
163 , "denarii"
164 )
165 )
166 -- It might print, for example, "You see a
167 -- Roman woman from Arucapa. She has black
168 -- hair and carries 433 denarii."
169 \end{lstlisting}
170
171 \end{document}
1 <document> ::= <group> <document> | ""
2 <group> ::= <pair> <group>
1 # Basic types
2
3 Rubbish features a typical set of built-in values: integers which
4 transparently become bignums, floating-point numbers, functions,
5 tuples, and the soon-to-be-explained variants. Every missing value
6 is represented by these. (Technically, numbers and tuples could
7 be subsumed by variants---and are, in the semantics---but practically,
8 they are treated as separate due to their wide use.)
9
10 Variants are an untyped version of the tagged sums featured in statically
11 typed functional languages. A variant always begins with either a capital
12 letter or a backtick.
13
14 Nullary variants are treated not unlike symbols in Lisps or Ruby. The
15 only meaningful operations on them are comparison or pattern-matching.
16 Certain nullary variants are used as distinguished values; for example,
17 `False` is the false value and `Void` is the return type of most
18 side-effectful functions. In addition, nullary variants subsume most
19 uses of enumerations and function as keywords, e.g.
20 `file := open("filename.txt", R)`.
21
22 However, variants can also take an arbitrary number of arguments. For
23 example, evaluating `Point(1, 2+3)` results in the value `Point(1, 5)`;
24 the intuition behind them can be captured in a number of ways: you
25 can think of an n-ary variant as an (n+1)-ary variant with a symbol
26 as its first element, or as
1 If I write my own shell---which I may very well do at some point---there's a
2 particular process model I'd like to embed in it. To wit: in UNIX right now, each
3 program in a pipeline has a single input stream and two output streams, with
4 files/sockets/&c for other kinds of communication.
5
6 A pipeline of functions `foo | bar | baz` looks kind of like this
7
8 keyboard -> stdin stdout -> stdin stdout -> stdin stdout ---+
9 >foo< >bar< >baz< |
10 stderr -+ stderr -+ stderr -+ |
11 | | | |
12 v v v v
13 [..................terminal...................]
14
15 Which works pretty well. You can do some pretty nice things with redirecting
16 stderr to here and stdin from there and so forth, and it enables some nice
17 terse shell invocations.
18
19 I'd like that basic system to be preserved, but with the ability to easily
20 create other _named_ streams. For example, imagine a hypothetical version of
21 wc which still outputs the relevant data to stdout, but also has three other
22 streams with these names:
23
24 / newlines
25 | words
26 stdin -> wc-s -> | bytes
27 | stdout
28 \ stderr
29
30 You can always see the normal output of wc on stdout:
31
32 gdsh$ wc-s *
33 2 3 6 this.txt
34 10 20 30 that.c
35 100 1000 10000 whatever.py
36
37 But you could also extract an individual stream from that invocation using
38 special redirection operators:
39
40 gdsh$ wc-s * stdout>/dev/null bytes>&stdout
41 3
42 20
43 1000
44
45 We could also have multiple input channels. I imagine an `fmt` command which
46 can interpolate named streams, e.g.
47
48 gdsh$ printf "1\n2 3\n" | wc-s | fmt "bytes: {bytes}\n words: {words}\n nl: {newlines}\n"
49 bytes: 6
50 words: 3
51 newlines: 2
52
53 We can then have a handful of other utilities and built-in shell operators for
54 manipulating these other streams:
55
56 gdsh$ wc-s * | select words
57 3
58 20
59 1000
60 gdsh$ fmt "this is {X}\n" X<this.txt
61 1
62 2 3
63 gdsh$ !X='cat this.txt' fmt "this is {X}\n"
64 1
65 2 3
66 gdsh$ @X=this.txt fmt "this is {X}\n"
67 1
68 2 3
69 gdsh$ ^Y=recidivism fmt "Y is {Y}\n"
70 recidivism
71 gdsh$ wc-s * words>words.txt bytes>bytes.txt newlines>newlines.txt
72 gdsh$ wc -s * | split words=[sort >sorted-word-count.txt] bytes=[uniq >uniq-bytes.txt]
73
74 This could also enable new idioms for programming, e.g. verbose output, rather than being
75 controlled by a flag, could be output consistently on another (usually hidden) stream:
76
77 gdsh$ myprog
78 myprog: config file not found
79 gdsh$ myprog stderr>/dev/null verbose>stderr
80 Setting up context
81 Looking in user dir... NOT FOUND
82 Looking in global dir... NOT FOUND
83 myprog: file not found
84 Tearing down context
85 Completed
86
87 Or maybe you could have human-readable error messages on `stderr` and
88 machine-readable error messages on `jsonerr`:
89
90 gdsh$ thatprog
91 ERROR: no filename given
92 gdsh$ thatprog stderr>/dev/null jsonerr>stderr
93 {"error-type":"fatal","error-code":30,"error-msg":"no filename given"}
94
95 There are other considerations I've glossed over here, but here are a few
96 notes, advantages, and interactions:
97
98 - I glossed over the distinction between input/output streams. In practice,
99 the _shell_ has no trouble disambiguating the two, but a given _program_
100 may wish to consider the distinction between `words.in` and `words.out`;
101 to this end, we could rename the existing streams `std.in` and `std.out` and
102 `err.out` (it being an error to read from `err.in` in most cases.[^2])
103
104 - This is obviously not POSIX-compliant, but could be made to work with the
105 existing UNIX process model by e.g. having a standard environment variable
106 for stream-aware programs to look at which maps stream names to file
107 descriptors. That way, programs which don't expect these special streams
108 still use fds 0, 1, and 2 as expected, while programs that do handle
109 these can read STREAM_DESC to find out which 'streams' correspond to which
110 file descriptors. In that case, you can _almost_ use these commands with
111 an existing shell by doing something like
112
113 sh$ echo foo >&77 | STREAM_DESC='std.in:0 std.out:1 err.out:2 foo.in:77' fmt "foo is {foo}\n"
114
115 - If we do use an existing UNIX system to write this, then we also should
116 integrate libraries for this, and the API for it is unknown. Presumably
117 a C interface would have `int get_stream(char* stream_name)` that could
118 return -1 on failure or something like it, but maybe another interface
119 would be preferable.
120
121 - This would interact really interestingly with a semi-graphical shell[^1] that
122 could visualize the stream relationships between commands as well as a
123 shell with a higher-level understanding of data types.[^3]
124
125 [^1]: Don't cringe. Look---the input device with the most information density
126 is the keyboard, right? That's why you use the command line at all. However,
127 graphical systems have more information density than pure-text systems. You
128 can take a pure-text system and extend it with position and color to give it
129 more information, and then with charts and graphs to give it _more_ information,
130 and so forth. What I'm proposing is _not_ drag-and-drop, although that might
131 be useful to some users; it's a keyboard-driven system that displays
132 information in a more dense, information-rich style. I keep thinking of building
133 this myself but for the massive herds of yaks I'd have to shave first.
134
135 [^2]: Actually, I can imagine a use for this. Let's say my programming language
136 of choice, Phosphorus, outputs its error messages in XML format, which is great
137 for an IDE, but I need to invoke it on a remote server which doesn't have my
138 IDE installed. I could
139 have a program `phWrapper` that passes all streams through unchanged _except_ for
140 `err.in`, which it parses as XML and then outputs as a kind of pretty-printed
141 trace representation to `err.out`. In that case,
142 `phosphorus my_source.ph | phWrapper` will give me the output as well as a
143 pretty-printed stack trace, while `phosphorus my_source.ph` will give me the
144 monstrous XML traces.
145
146 [^3]: PowerShell is the usual example given here, but I confess I haven't used
147 it. Effectively, all Unix processes expect to be given streams of raw text and
148 output streams of raw text. Text is versatile, which is nice, but it would
149 also be nice to have some basic structured data that isn't just streams of
150 text. For example, imagine a Lisp-based system that expected everything to be
151 S-expressions, so you can _guarantee_ that a program will get called with a
152 valid s-expression as input. Now, `wc` would output something like
153
154 lsh$ (wc "this.txt" "that.c")
155 (("this.txt" 2 4 6) ("that.c" 1 10 100))
156
157 or a system in which the data transmitted is JSON, as in
158
159 jsh$ wc ["this.txt", "that.c"]
160 { 'this.txt': {'newlines': 2, 'words': 4, 'characters': 6 }
161 , 'that.c': {'newlines': 1, 'words': 10, 'characters': 100}
162 }
163
164 Among other things, this means that you could cut down on the amount of
165 serializing/deserializing that a system is doing and just pass in some raw
166 in-memory structure. You also get some nice operations that subsume the role
167 of `sed` and `awk` and its ilk, e.g.
168
169 lsh$ (map (\ ((fname nl wd cs)) (fmt #f "%s has %d bytes." fname cs))
170 (wc "this.txt" "that.c"))
171 ("this.txt has 6 bytes" "that.c has 100 bytes")
172
173 You could then use piping and shells to do some _pretty_ elaborate stuff,
174 including transferring higher-level objects (like HTTP requests or opaque
175 OS-level objects like users or processes) in a universally understood way.
176
177 jsh$ ps | map (fun (x) { return [x.cmd, x.args, x.pid, x.user]; })
178 [ ["jsh", [], 4440, "gdritter"]
179 , ["emacs", ["post.md"], 12893, "gdritter"]
180 , ["ps", [], 29678, "gdritter"]
181 ]
182
183 So those are some ideas that have been drifting around in my head for a while.
184 No idea if I'll ever implement any of them, or if they'd even be worth
185 implementing, but I might get around to it at some point. We'll see.
1 data :list (a)
2 | :cons { a -o (box (:list a)) -o (:list a) }
3 | :nil (:list a)
4
5 def sum { (:list int) -> int }
6 | (:nil) => 0
7 | (:cons x xs) => { x + (sum (deref xs)) }
8
9 def range { int -> int -> (:list int) }
10 |
1 -- In this case, a^n corresponds to an n-tuple whose elements are of type a
2 -- We have a typeclass
3
4 class Conv a b where conv :: a -> b
5
6 -- And types corresponding to
7
8 type Tup t = forall n : Nat. t ^ n
9 type Var t = forall n : Nat. (forall s. Conv s t => s) ^ n
10
11 -- we also have these two built-in primitives
12
13 generalize :: Var t -> Tup t
14 forget :: Tup t -> [t]
15
16 -- that will usually be used together
17
18 var :: Var t -> [t]
19
20 -- We could use this to create 'variadic' functions, e.g.
21 -- a non-type-safe but C-like printf
22
23 data PFElem = PFInt Int | PFFloat Float | PFString String
24
25 instance Conv Int PFElem where conv = PFInt
26 instance Conv Float PFElem where conv = PFFloat
27 instance Conv String PFElem where conv = PFString
28
29 printf :: String -> Var PFElem -> IO ()
30 printf spec args = putStr . printfInterpolate . var $ args
31 where printfInterpolate :: String -> [PFElem] -> String
32 printfInterpolate = {- ... -}
33
34 main :: IO ()
35 main = printf "string %s and int %d\n" ("foo", 5)
36
37 --------------------------------------------------------------------------------
38
39 -- In Latka, we will have the `Var` type and the `var` primitive as built-ins,
40 -- and treats a^n as a subtype of Var a in all cases. Thus, we can convert a
41 -- value like <2,3> : Nat * Nat to a value of type Var Nat, but not the other
42 -- way around.
43
44 -- With that machinery, we can define the following as a library:
45
46 -- word
47 instance Conv String WordPart where {- ... -}
48 instance Conv Word WordPart where {- ... -}
49
50 wd : Var WordPart -> Word
51 wd = {- ... -}
52
53 -- this is well-typed, because its argument is of type String * Word <: Var WordPart
54 puts wd.<"foo", wd.<"bar", "baz">>
55 -- this is not, because its argument is of type String * String * (Nat * Nat)
56 -- puts wd.<"a","b",<1,2>>
57
58
59 -- sentence
60 instance Conv String SentencePart where {- ... -}
61 instance Conv Word SentencePart where {- ... -}
62 instance Conv Sentence SentencePart where {- ... -}
63
64 se : Var SentencePart -> Sentence
65 se = {- ... -}
66
67 -- this is well-typed, as the argument is of type
68 -- String * Word * Sentence <: Var SentencePart
69 puts se.<"one", wd.<"two", "three">, se.<"four", "five">>
70
71 -- &c
72
73 --this gives us type-safe variadic functions, as well as a nice mechanism for
74 --semi-implicitly stringifying things
75
76 cat : Var String -> String
77 cat vs = go.(var.vs)
78 where go [] = ""
79 go (x::xs) = x (go.xs)
80
81 {- prints "foo5<>" -}
82 puts cat.<"foo", 5, <>>
1 (* Axiom: all extant Latka programs without functions should continue
2 * to work the same way, e.g. *)
3
4 cons = $(p t k w h)
5 vowel = $(a e i o u) (4: "" | "")
6 syll = 4: cons vowel | vowel
7 puts syll (rep.6.syll)
8
9 (* We can now define rep in-language as
10 * rep (fixed n : Nat) (s : String) : String =
11 case n of
12 0 . ""
13 1 . s
14 _ . s (rep.(pred.n).s)
15 *)
16
17 (* It is acceptable that all arguments must be explicitly specified. *)
18
19 fact (fixed n : Nat) : Nat=
20 case n of
21 0 . 0
22 _ . mul.n.(fact.(pred.n))
23
24 (* We have both labeled sums and records, where the records are closer to
25 * the ML style than the Haskell style. `alias` is Haskell's `type` *)
26
27 alias Person =
28 { name : String
29 , age : Nat
30 }
31
32 data Character =
33 Character Person
34
35 (* Type variables work as in Haskell *)
36
37 data List a = Nil | Cons a (List a)
38
39 map (f : a -> b) (ls : List a) =
40 case ls of
41 Cons x xs . Cons.(f.x).xs
42 _ . Nil
43
44 (* We can explicitly indicate a subtyping (convertibility)
45 * relationship, so long as our conv instances form a DAG. *)
46
47 data TaggedList a = TNil | TCons a String (List a)
48
49 conv TaggedList a <: List a where
50 coerce ls = case ls of
51 TCons x _ xs . Cons x xs
52 TNil . Nil
53
54 (* Among other things, this is used in variadic functions parameters. *)
55
56 appendAll (lsts : Variadic (List a)) : List a =
57 let go ls = case ls of
58 [] . []
59 (x::xs) . append.x.(go.xs)
60 in go lsts
61
62 puts cat.(appendAll.(["x","y"], TCons "z" TNil))