Commit | Line | Data |
---|---|---|
7f918cf1 CE |
1 | OptionalArguments |
2 | ================= | |
3 | ||
4 | <:StandardML:Standard ML> does not have built-in support for optional | |
5 | arguments. Nevertheless, using <:Fold:>, it is easy to define | |
6 | functions that take optional arguments. | |
7 | ||
8 | For example, suppose that we have the following definition of a | |
9 | function `f`. | |
10 | ||
11 | [source,sml] | |
12 | ---- | |
13 | fun f (i, r, s) = | |
14 | concat [Int.toString i, ", ", Real.toString r, ", ", s] | |
15 | ---- | |
16 | ||
17 | Using the `OptionalArg` structure described below, we can define a | |
18 | function `f'`, an optionalized version of `f`, that takes 0, 1, 2, or | |
19 | 3 arguments. Embedded within `f'` will be default values for `i`, | |
20 | `r`, and `s`. If `f'` gets no arguments, then all the defaults are | |
21 | used. If `f'` gets one argument, then that will be used for `i`. Two | |
22 | arguments will be used for `i` and `r` respectively. Three arguments | |
23 | will override all default values. Calls to `f'` will look like the | |
24 | following. | |
25 | ||
26 | [source,sml] | |
27 | ---- | |
28 | f' $ | |
29 | f' `2 $ | |
30 | f' `2 `3.0 $ | |
31 | f' `2 `3.0 `"four" $ | |
32 | ---- | |
33 | ||
34 | The optional argument indicator, +`+, is not special syntax --- | |
35 | it is a normal SML value, defined in the `OptionalArg` structure | |
36 | below. | |
37 | ||
38 | Here is the definition of `f'` using the `OptionalArg` structure, in | |
39 | particular, `OptionalArg.make` and `OptionalArg.D`. | |
40 | ||
41 | [source,sml] | |
42 | ---- | |
43 | val f' = | |
44 | fn z => | |
45 | let open OptionalArg in | |
46 | make (D 1) (D 2.0) (D "three") $ | |
47 | end (fn i & r & s => f (i, r, s)) | |
48 | z | |
49 | ---- | |
50 | ||
51 | The definition of `f'` is eta expanded as with all uses of fold. A | |
52 | call to `OptionalArg.make` is supplied with a variable number of | |
53 | defaults (in this case, three), the end-of-arguments terminator, `$`, | |
54 | and the function to run, taking its arguments as an n-ary | |
55 | <:ProductType:product>. In this case, the function simply converts | |
56 | the product to an ordinary tuple and calls `f`. Often, the function | |
57 | body will simply be written directly. | |
58 | ||
59 | In general, the definition of an optional-argument function looks like | |
60 | the following. | |
61 | ||
62 | [source,sml] | |
63 | ---- | |
64 | val f = | |
65 | fn z => | |
66 | let open OptionalArg in | |
67 | make (D <default1>) (D <default2>) ... (D <defaultn>) $ | |
68 | end (fn x1 & x2 & ... & xn => | |
69 | <function code goes here>) | |
70 | z | |
71 | ---- | |
72 | ||
73 | Here is the definition of `OptionalArg`. | |
74 | ||
75 | [source,sml] | |
76 | ---- | |
77 | structure OptionalArg = | |
78 | struct | |
79 | val make = | |
80 | fn z => | |
81 | Fold.fold | |
82 | ((id, fn (f, x) => f x), | |
83 | fn (d, r) => fn func => | |
84 | Fold.fold ((id, d ()), fn (f, d) => | |
85 | let | |
86 | val d & () = r (id, f d) | |
87 | in | |
88 | func d | |
89 | end)) | |
90 | z | |
91 | ||
92 | fun D d = Fold.step0 (fn (f, r) => | |
93 | (fn ds => f (d & ds), | |
94 | fn (f, a & b) => r (fn x => f a & x, b))) | |
95 | ||
96 | val ` = | |
97 | fn z => | |
98 | Fold.step1 (fn (x, (f, _ & d)) => (fn d => f (x & d), d)) | |
99 | z | |
100 | end | |
101 | ---- | |
102 | ||
103 | `OptionalArg.make` uses a nested fold. The first `fold` accumulates | |
104 | the default values in a product, associated to the right, and a | |
105 | reversal function that converts a product (of the same arity as the | |
106 | number of defaults) from right associativity to left associativity. | |
107 | The accumulated defaults are used by the second fold, which recurs | |
108 | over the product, replacing the appropriate component as it encounters | |
109 | optional arguments. The second fold also constructs a "fill" | |
110 | function, `f`, that is used to reconstruct the product once the | |
111 | end-of-arguments is reached. Finally, the finisher reconstructs the | |
112 | product and uses the reversal function to convert the product from | |
113 | right associative to left associative, at which point it is passed to | |
114 | the user-supplied function. | |
115 | ||
116 | Much of the complexity comes from the fact that while recurring over a | |
117 | product from left to right, one wants it to be right-associative, | |
118 | e.g., look like | |
119 | ||
120 | [source,sml] | |
121 | ---- | |
122 | a & (b & (c & d)) | |
123 | ---- | |
124 | ||
125 | but the user function in the end wants the product to be left | |
126 | associative, so that the product argument pattern can be written | |
127 | without parentheses (since `&` is left associative). | |
128 | ||
129 | ||
130 | == Labelled optional arguments == | |
131 | ||
132 | In addition to the positional optional arguments described above, it | |
133 | is sometimes useful to have labelled optional arguments. These allow | |
134 | one to define a function, `f`, with defaults, say `a` and `b`. Then, | |
135 | a caller of `f` can supply values for `a` and `b` by name. If no | |
136 | value is supplied then the default is used. | |
137 | ||
138 | Labelled optional arguments are a simple extension of | |
139 | <:FunctionalRecordUpdate:> using post composition. Suppose, for | |
140 | example, that one wants a function `f` with labelled optional | |
141 | arguments `a` and `b` with default values `0` and `0.0` respectively. | |
142 | If one has a functional-record-update function `updateAB` for records | |
143 | with `a` and `b` fields, then one can define `f` in the following way. | |
144 | ||
145 | [source,sml] | |
146 | ---- | |
147 | val f = | |
148 | fn z => | |
149 | Fold.post | |
150 | (updateAB {a = 0, b = 0.0}, | |
151 | fn {a, b} => print (concat [Int.toString a, " ", | |
152 | Real.toString b, "\n"])) | |
153 | z | |
154 | ---- | |
155 | ||
156 | The idea is that `f` is the post composition (using `Fold.post`) of | |
157 | the actual code for the function with a functional-record updater that | |
158 | starts with the defaults. | |
159 | ||
160 | Here are some example calls to `f`. | |
161 | [source,sml] | |
162 | ---- | |
163 | val () = f $ | |
164 | val () = f (U#a 13) $ | |
165 | val () = f (U#a 13) (U#b 17.5) $ | |
166 | val () = f (U#b 17.5) (U#a 13) $ | |
167 | ---- | |
168 | ||
169 | Notice that a caller can supply neither of the arguments, either of | |
170 | the arguments, or both of the arguments, and in either order. All | |
171 | that matter is that the arguments be labelled correctly (and of the | |
172 | right type, of course). | |
173 | ||
174 | Here is another example. | |
175 | ||
176 | [source,sml] | |
177 | ---- | |
178 | val f = | |
179 | fn z => | |
180 | Fold.post | |
181 | (updateBCD {b = 0, c = 0.0, d = "<>"}, | |
182 | fn {b, c, d} => | |
183 | print (concat [Int.toString b, " ", | |
184 | Real.toString c, " ", | |
185 | d, "\n"])) | |
186 | z | |
187 | ---- | |
188 | ||
189 | Here are some example calls. | |
190 | ||
191 | [source,sml] | |
192 | ---- | |
193 | val () = f $ | |
194 | val () = f (U#d "goodbye") $ | |
195 | val () = f (U#d "hello") (U#b 17) (U#c 19.3) $ | |
196 | ---- |