1 TipsForWritingConciseSML
2 ========================
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
10 == Datatypes in Signatures ==
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,
19 datatype exp = Fn of id * exp | App of exp * exp | Var of id
23 is just a specification of a datatype that may be matched by multiple
24 (albeit identical) datatype declarations. For example, in
28 structure AnExp : EXP = struct
29 datatype exp = Fn of id * exp | App of exp * exp | Var of id
32 structure AnotherExp : EXP = struct
33 datatype exp = Fn of id * exp | App of exp * exp | Var of id
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:
43 structure Exp = struct
44 datatype exp = Fn of id * exp | App of exp * exp | Var of id
48 datatype exp = datatype Exp.exp
51 structure Exp : EXP = struct
56 Keep in mind that this isn't semantically equivalent to the original.
59 == Clausal Function Definitions ==
61 The syntax of clausal function definitions is rather repetitive. For
66 fun isSome NONE = false
67 | isSome (SOME _) = true
79 For recursive functions the break-even point is one clause higher. For example,
85 | fib n = fib (n-1) + fib (n-2)
88 isn't less verbose than
95 | n => fib (n-1) + fib (n-2)
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
105 fun eval (Fn (v, b)) env => ...
106 | eval (App (f, a) env => ...
107 | eval (Var v) env => ...
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
130 (square (5 + 1)) + (square (5 * 2))
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:
141 if (condition) then ... else ...
142 while (condition) do ...
145 The same basically applies to case expressions:
149 case (expression) of ...
152 It is not uncommon to match a tuple of two or more values:
161 Such case expressions can be written more concisely with an
162 <:ProductType:infix product constructor>:
174 Repeated sequences of conditionals such as
179 else if x = y then ...
183 can often be written more concisely as case expressions such as
187 case Int.compare (x, y) of
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
209 fun comparing (x, y) {lt, eq, gt} =
210 (case Int.compare (x, y) of
216 An advantage is that no datatype definition is needed. A disadvantage
217 is that you can't combine multiple dispatch results easily.
220 == Command-Query Fusion ==
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
229 val isEmpty : 'a t -> bool
233 and use of a stack would look like
238 then ... pop stack ...
242 or, when the element needs to be named,
247 then let val elem = pop stack in ... end
251 For efficiency, correctness, and conciseness, it is often better to
252 combine the query and command and return the result as an option:
256 val pop : 'a t -> 'a option
259 A use of a stack would then look like this: