Backport from sid to buster
[hcoop/debian/mlton.git] / doc / guide / src / TipsForWritingConciseSML.adoc
1 TipsForWritingConciseSML
2 ========================
3
4 SML is a rich enough language that there are often several ways to
5 express things. This page contains miscellaneous tips (ideas not
6 rules) for writing concise SML. The metric that we are interested in
7 here is the number of tokens or words (rather than the number of
8 lines, for example).
9
10 == Datatypes in Signatures ==
11
12 A seemingly frequent source of repetition in SML is that of datatype
13 definitions in signatures and structures. Actually, it isn't
14 repetition at all. A datatype specification in a signature, such as,
15
16 [source,sml]
17 ----
18 signature EXP = sig
19 datatype exp = Fn of id * exp | App of exp * exp | Var of id
20 end
21 ----
22
23 is just a specification of a datatype that may be matched by multiple
24 (albeit identical) datatype declarations. For example, in
25
26 [source,sml]
27 ----
28 structure AnExp : EXP = struct
29 datatype exp = Fn of id * exp | App of exp * exp | Var of id
30 end
31
32 structure AnotherExp : EXP = struct
33 datatype exp = Fn of id * exp | App of exp * exp | Var of id
34 end
35 ----
36
37 the types `AnExp.exp` and `AnotherExp.exp` are two distinct types. If
38 such <:GenerativeDatatype:generativity> isn't desired or needed, you
39 can avoid the repetition:
40
41 [source,sml]
42 ----
43 structure Exp = struct
44 datatype exp = Fn of id * exp | App of exp * exp | Var of id
45 end
46
47 signature EXP = sig
48 datatype exp = datatype Exp.exp
49 end
50
51 structure Exp : EXP = struct
52 open Exp
53 end
54 ----
55
56 Keep in mind that this isn't semantically equivalent to the original.
57
58
59 == Clausal Function Definitions ==
60
61 The syntax of clausal function definitions is rather repetitive. For
62 example,
63
64 [source,sml]
65 ----
66 fun isSome NONE = false
67 | isSome (SOME _) = true
68 ----
69
70 is more verbose than
71
72 [source,sml]
73 ----
74 val isSome =
75 fn NONE => false
76 | SOME _ => true
77 ----
78
79 For recursive functions the break-even point is one clause higher. For example,
80
81 [source,sml]
82 ----
83 fun fib 0 = 0
84 | fib 1 = 1
85 | fib n = fib (n-1) + fib (n-2)
86 ----
87
88 isn't less verbose than
89
90 [source,sml]
91 ----
92 val rec fib =
93 fn 0 => 0
94 | 1 => 1
95 | n => fib (n-1) + fib (n-2)
96 ----
97
98 It is quite often the case that a curried function primarily examines
99 just one of its arguments. Such functions can be written particularly
100 concisely by making the examined argument last. For example, instead
101 of
102
103 [source,sml]
104 ----
105 fun eval (Fn (v, b)) env => ...
106 | eval (App (f, a) env => ...
107 | eval (Var v) env => ...
108 ----
109
110 consider writing
111
112 [source,sml]
113 ----
114 fun eval env =
115 fn Fn (v, b) => ...
116 | App (f, a) => ...
117 | Var v => ...
118 ----
119
120
121 == Parentheses ==
122
123 It is a good idea to avoid using lots of irritating superfluous
124 parentheses. An important rule to know is that prefix function
125 application in SML has higher precedence than any infix operator. For
126 example, the outer parentheses in
127
128 [source,sml]
129 ----
130 (square (5 + 1)) + (square (5 * 2))
131 ----
132
133 are superfluous.
134
135 People trained in other languages often use superfluous parentheses in
136 a number of places. In particular, the parentheses in the following
137 examples are practically always superfluous and are best avoided:
138
139 [source,sml]
140 ----
141 if (condition) then ... else ...
142 while (condition) do ...
143 ----
144
145 The same basically applies to case expressions:
146
147 [source,sml]
148 ----
149 case (expression) of ...
150 ----
151
152 It is not uncommon to match a tuple of two or more values:
153
154 [source,sml]
155 ----
156 case (a, b) of
157 (A1, B1) => ...
158 | (A2, B2) => ...
159 ----
160
161 Such case expressions can be written more concisely with an
162 <:ProductType:infix product constructor>:
163
164 [source,sml]
165 ----
166 case a & b of
167 A1 & B1 => ...
168 | A2 & B2 => ...
169 ----
170
171
172 == Conditionals ==
173
174 Repeated sequences of conditionals such as
175
176 [source,sml]
177 ----
178 if x < y then ...
179 else if x = y then ...
180 else ...
181 ----
182
183 can often be written more concisely as case expressions such as
184
185 [source,sml]
186 ----
187 case Int.compare (x, y) of
188 LESS => ...
189 | EQUAL => ...
190 | GREATER => ...
191 ----
192
193 For a custom comparison, you would then define an appropriate datatype
194 and a reification function. An alternative to using datatypes is to
195 use dispatch functions
196
197 [source,sml]
198 ----
199 comparing (x, y)
200 {lt = fn () => ...,
201 eq = fn () => ...,
202 gt = fn () => ...}
203 ----
204
205 where
206
207 [source,sml]
208 ----
209 fun comparing (x, y) {lt, eq, gt} =
210 (case Int.compare (x, y) of
211 LESS => lt
212 | EQUAL => eq
213 | GREATER => gt) ()
214 ----
215
216 An advantage is that no datatype definition is needed. A disadvantage
217 is that you can't combine multiple dispatch results easily.
218
219
220 == Command-Query Fusion ==
221
222 Many are familiar with the
223 http://en.wikipedia.org/wiki/Command-Query_Separation[Command-Query
224 Separation Principle]. Adhering to the principle, a signature for an
225 imperative stack might contain specifications
226
227 [source,sml]
228 ----
229 val isEmpty : 'a t -> bool
230 val pop : 'a t -> 'a
231 ----
232
233 and use of a stack would look like
234
235 [source,sml]
236 ----
237 if isEmpty stack
238 then ... pop stack ...
239 else ...
240 ----
241
242 or, when the element needs to be named,
243
244 [source,sml]
245 ----
246 if isEmpty stack
247 then let val elem = pop stack in ... end
248 else ...
249 ----
250
251 For efficiency, correctness, and conciseness, it is often better to
252 combine the query and command and return the result as an option:
253
254 [source,sml]
255 ----
256 val pop : 'a t -> 'a option
257 ----
258
259 A use of a stack would then look like this:
260
261 [source,sml]
262 ----
263 case pop stack of
264 NONE => ...
265 | SOME elem => ...
266 ----