Commit | Line | Data |
---|---|---|
7f918cf1 CE |
1 | InfixingOperators |
2 | ================= | |
3 | ||
4 | Fixity specifications are not part of signatures in | |
5 | <:StandardML:Standard ML>. When one wants to use a module that | |
6 | provides functions designed to be used as infix operators there are | |
7 | several obvious alternatives: | |
8 | ||
9 | * Use only prefix applications. Unfortunately there are situations | |
10 | where infix applications lead to considerably more readable code. | |
11 | ||
12 | * Make the fixity declarations at the top-level. This may lead to | |
13 | collisions and may be unsustainable in a large project. Pollution of | |
14 | the top-level should be avoided. | |
15 | ||
16 | * Make the fixity declarations at each scope where you want to use | |
17 | infix applications. The duplication becomes inconvenient if the | |
18 | operators are widely used. Duplication of code should be avoided. | |
19 | ||
20 | * Use non-standard extensions, such as the <:MLBasis: ML Basis system> | |
21 | to control the scope of fixity declarations. This has the obvious | |
22 | drawback of reduced portability. | |
23 | ||
24 | * Reuse existing infix operator symbols (`^`, `+`, `-`, ...). This | |
25 | can be convenient when the standard operators aren't needed in the | |
26 | same scope with the new operators. On the other hand, one is limited | |
27 | to the standard operator symbols and the code may appear confusing. | |
28 | ||
29 | None of the obvious alternatives is best in every case. The following | |
30 | describes a slightly less obvious alternative that can sometimes be | |
31 | useful. The idea is to approximate Haskell's special syntax for | |
32 | treating any identifier enclosed in grave accents (backquotes) as an | |
33 | infix operator. In Haskell, instead of writing the prefix application | |
34 | `f x y` one can write the infix application ++x `f` y++. | |
35 | ||
36 | ||
37 | == Infixing operators == | |
38 | ||
39 | Let's first take a look at the definitions of the operators: | |
40 | ||
41 | [source,sml] | |
42 | ---- | |
43 | infix 3 <\ fun x <\ f = fn y => f (x, y) (* Left section *) | |
44 | infix 3 \> fun f \> y = f y (* Left application *) | |
45 | infixr 3 /> fun f /> y = fn x => f (x, y) (* Right section *) | |
46 | infixr 3 </ fun x </ f = f x (* Right application *) | |
47 | ||
48 | infix 2 o (* See motivation below *) | |
49 | infix 0 := | |
50 | ---- | |
51 | ||
52 | The left and right sectioning operators, `<\` and `/>`, are useful in | |
53 | SML for partial application of infix operators. | |
54 | <!Cite(Paulson96, ML For the Working Programmer)> describes curried | |
55 | functions `secl` and `secr` for the same purpose on pages 179-181. | |
56 | For example, | |
57 | ||
58 | [source,sml] | |
59 | ---- | |
60 | List.map (op- /> y) | |
61 | ---- | |
62 | ||
63 | is a function for subtracting `y` from a list of integers and | |
64 | ||
65 | [source,sml] | |
66 | ---- | |
67 | List.exists (x <\ op=) | |
68 | ---- | |
69 | ||
70 | is a function for testing whether a list contains an `x`. | |
71 | ||
72 | Together with the left and right application operators, `\>` and `</`, | |
73 | the sectioning operators provide a way to treat any binary function | |
74 | (i.e. a function whose domain is a pair) as an infix operator. In | |
75 | general, | |
76 | ||
77 | ---- | |
78 | x0 <\f1\> x1 <\f2\> x2 ... <\fN\> xN = fN (... f2 (f1 (x0, x1), x2) ..., xN) | |
79 | ---- | |
80 | ||
81 | and | |
82 | ||
83 | ---- | |
84 | xN </fN/> ... x2 </f2/> x1 </f1/> x0 = fN (xN, ... f2 (x2, f1 (x1, x0)) ...) | |
85 | ---- | |
86 | ||
87 | ||
88 | === Examples === | |
89 | ||
90 | As a fairly realistic example, consider providing a function for sequencing | |
91 | comparisons: | |
92 | ||
93 | [source,sml] | |
94 | ---- | |
95 | structure Order (* ... *) = | |
96 | struct | |
97 | (* ... *) | |
98 | val orWhenEq = fn (EQUAL, th) => th () | |
99 | | (other, _) => other | |
100 | (* ... *) | |
101 | end | |
102 | ---- | |
103 | Using `orWhenEq` and the infixing operators, one can write a | |
104 | `compare` function for triples as | |
105 | ||
106 | [source,sml] | |
107 | ---- | |
108 | fun compare (fad, fbe, fcf) ((a, b, c), (d, e, f)) = | |
109 | fad (a, d) <\Order.orWhenEq\> `fbe (b, e) <\Order.orWhenEq\> `fcf (c, f) | |
110 | ---- | |
111 | ||
112 | where +`+ is defined as | |
113 | ||
114 | [source,sml] | |
115 | ---- | |
116 | fun `f x = fn () => f x | |
117 | ---- | |
118 | ||
119 | Although `orWhenEq` can be convenient (try rewriting the above without | |
120 | it), it is probably not useful enough to be defined at the top level | |
121 | as an infix operator. Fortunately we can use the infixing operators | |
122 | and don't have to. | |
123 | ||
124 | Another fairly realistic example would be to use the infixing operators with | |
125 | the technique described on the <:Printf:> page. Assuming that you would have | |
126 | a `Printf` module binding `printf`, +`+, and formatting combinators | |
127 | named `int` and `string`, you could write | |
128 | ||
129 | [source,sml] | |
130 | ---- | |
131 | let open Printf in | |
132 | printf (`"Here's an int "<\int\>" and a string "<\string\>".") 13 "foo" end | |
133 | ---- | |
134 | ||
135 | without having to duplicate the fixity declarations. Alternatively, you could | |
136 | write | |
137 | ||
138 | [source,sml] | |
139 | ---- | |
140 | P.printf (P.`"Here's an int "<\P.int\>" and a string "<\P.string\>".") 13 "foo" | |
141 | ---- | |
142 | ||
143 | assuming you have the made the binding | |
144 | ||
145 | [source,sml] | |
146 | ---- | |
147 | structure P = Printf | |
148 | ---- | |
149 | ||
150 | ||
151 | == Application and piping operators == | |
152 | ||
153 | The left and right application operators may also provide some notational | |
154 | convenience on their own. In general, | |
155 | ||
156 | ---- | |
157 | f \> x1 \> ... \> xN = f x1 ... xN | |
158 | ---- | |
159 | ||
160 | and | |
161 | ||
162 | ---- | |
163 | xN </ ... </ x1 </ f = f x1 ... xN | |
164 | ---- | |
165 | ||
166 | If nothing else, both of them can eliminate parentheses. For example, | |
167 | ||
168 | [source,sml] | |
169 | ---- | |
170 | foo (1 + 2) = foo \> 1 + 2 | |
171 | ---- | |
172 | ||
173 | The left and right application operators are related to operators | |
174 | that could be described as the right and left piping operators: | |
175 | ||
176 | [source,sml] | |
177 | ---- | |
178 | infix 1 >| val op>| = op</ (* Left pipe *) | |
179 | infixr 1 |< val op|< = op\> (* Right pipe *) | |
180 | ---- | |
181 | ||
182 | As you can see, the left and right piping operators, `>|` and `|<`, | |
183 | are the same as the right and left application operators, | |
184 | respectively, except the associativities are reversed and the binding | |
185 | strength is lower. They are useful for piping data through a sequence | |
186 | of operations. In general, | |
187 | ||
188 | ---- | |
189 | x >| f1 >| ... >| fN = fN (... (f1 x) ...) = (fN o ... o f1) x | |
190 | ---- | |
191 | ||
192 | and | |
193 | ||
194 | ---- | |
195 | fN |< ... |< f1 |< x = fN (... (f1 x) ...) = (fN o ... o f1) x | |
196 | ---- | |
197 | ||
198 | The right piping operator, `|<`, is provided by the Haskell prelude as | |
199 | `$`. It can be convenient in CPS or continuation passing style. | |
200 | ||
201 | A use for the left piping operator is with parsing combinators. In a | |
202 | strict language, like SML, eta-reduction is generally unsafe. Using | |
203 | the left piping operator, parsing functions can be formatted | |
204 | conveniently as | |
205 | ||
206 | [source,sml] | |
207 | ---- | |
208 | fun parsingFunc input = | |
209 | input >| (* ... *) | |
210 | || (* ... *) | |
211 | || (* ... *) | |
212 | ---- | |
213 | ||
214 | where `||` is supposed to be a combinator provided by the parsing combinator | |
215 | library. | |
216 | ||
217 | ||
218 | == About precedences == | |
219 | ||
220 | You probably noticed that we redefined the | |
221 | <:OperatorPrecedence:precedences> of the function composition operator | |
222 | `o` and the assignment operator `:=`. Doing so is not strictly | |
223 | necessary, but can be convenient and should be relatively | |
224 | safe. Consider the following motivating examples from | |
225 | <:WesleyTerpstra: Wesley W. Terpstra> relying on the redefined | |
226 | precedences: | |
227 | ||
228 | [source,sml] | |
229 | ---- | |
230 | Word8.fromInt o Char.ord o s <\String.sub | |
231 | (* Combining sectioning and composition *) | |
232 | ||
233 | x := s <\String.sub\> i | |
234 | (* Assigning the result of an infixed application *) | |
235 | ---- | |
236 | ||
237 | In imperative languages, assignment usually has the lowest precedence | |
238 | (ignoring statement separators). The precedence of `:=` in the | |
239 | <:BasisLibrary: Basis Library> is perhaps unnecessarily high, because | |
240 | an expression of the form `r := x` always returns a unit, which makes | |
241 | little sense to combine with anything. Dropping `:=` to the lowest | |
242 | precedence level makes it behave more like in other imperative | |
243 | languages. | |
244 | ||
245 | The case for `o` is different. With the exception of `before` and | |
246 | `:=`, it doesn't seem to make much sense to use `o` with any of the | |
247 | operators defined by the <:BasisLibrary: Basis Library> in an | |
248 | unparenthesized expression. This is simply because none of the other | |
249 | operators deal with functions. It would seem that the precedence of | |
250 | `o` could be chosen completely arbitrarily from the set `{1, ..., 9}` | |
251 | without having any adverse effects with respect to other infix | |
252 | operators defined by the <:BasisLibrary: Basis Library>. | |
253 | ||
254 | ||
255 | == Design of the symbols == | |
256 | ||
257 | The closest approximation of Haskell's ++x `f` y++ syntax | |
258 | achievable in Standard ML would probably be something like | |
259 | ++x `f^ y++, but `^` is already used for string | |
260 | concatenation by the <:BasisLibrary: Basis Library>. Other | |
261 | combinations of the characters +`+ and `^` would be | |
262 | possible, but none seems clearly the best visually. The symbols `<\`, | |
263 | `\>`, `</`, and `/>` are reasonably concise and have a certain | |
264 | self-documenting appearance and symmetry, which can help to remember | |
265 | them. As the names suggest, the symbols of the piping operators `>|` | |
266 | and `|<` are inspired by Unix shell pipelines. | |
267 | ||
268 | ||
269 | == Also see == | |
270 | ||
271 | * <:Utilities:> |