Commit | Line | Data |
---|---|---|
7f918cf1 CE |
1 | NumericLiteral |
2 | ============== | |
3 | ||
4 | Numeric literals in <:StandardML:Standard ML> can be written in either | |
5 | decimal or hexadecimal notation. Sometimes it can be convenient to | |
6 | write numbers down in other bases. Fortunately, using <:Fold:>, it is | |
7 | possible to define a concise syntax for numeric literals that allows | |
8 | one to write numeric constants in any base and of various types | |
9 | (`int`, `IntInf.int`, `word`, and more). | |
10 | ||
11 | We will define constants `I`, `II`, `W`, and +`+ so | |
12 | that, for example, | |
13 | [source,sml] | |
14 | ---- | |
15 | I 10 `1`2`3 $ | |
16 | ---- | |
17 | denotes `123:int` in base 10, while | |
18 | [source,sml] | |
19 | ---- | |
20 | II 8 `2`3 $ | |
21 | ---- | |
22 | denotes `19:IntInf.int` in base 8, and | |
23 | [source,sml] | |
24 | ---- | |
25 | W 2 `1`1`0`1 $ | |
26 | ---- | |
27 | denotes `0w13: word`. | |
28 | ||
29 | Here is the code. | |
30 | ||
31 | [source,sml] | |
32 | ---- | |
33 | structure Num = | |
34 | struct | |
35 | fun make (op *, op +, i2x) iBase = | |
36 | let | |
37 | val xBase = i2x iBase | |
38 | in | |
39 | Fold.fold | |
40 | ((i2x 0, | |
41 | fn (i, x) => | |
42 | if 0 <= i andalso i < iBase then | |
43 | x * xBase + i2x i | |
44 | else | |
45 | raise Fail (concat | |
46 | ["Num: ", Int.toString i, | |
47 | " is not a valid\ | |
48 | \ digit in base ", | |
49 | Int.toString iBase])), | |
50 | fst) | |
51 | end | |
52 | ||
53 | fun I ? = make (op *, op +, id) ? | |
54 | fun II ? = make (op *, op +, IntInf.fromInt) ? | |
55 | fun W ? = make (op *, op +, Word.fromInt) ? | |
56 | ||
57 | fun ` ? = Fold.step1 (fn (i, (x, step)) => | |
58 | (step (i, x), step)) ? | |
59 | ||
60 | val a = 10 | |
61 | val b = 11 | |
62 | val c = 12 | |
63 | val d = 13 | |
64 | val e = 14 | |
65 | val f = 15 | |
66 | end | |
67 | ---- | |
68 | where | |
69 | [source,sml] | |
70 | ---- | |
71 | fun fst (x, _) = x | |
72 | ---- | |
73 | ||
74 | The idea is for the fold to start with zero and to construct the | |
75 | result one digit at a time, with each stepper multiplying the previous | |
76 | result by the base and adding the next digit. The code is abstracted | |
77 | in two different ways for extra generality. First, the `make` | |
78 | function abstracts over the various primitive operations (addition, | |
79 | multiplication, etc) that are needed to construct a number. This | |
80 | allows the same code to be shared for constants `I`, `II`, `W` used to | |
81 | write down the various numeric types. It also allows users to add new | |
82 | constants for additional numeric types, by supplying the necessary | |
83 | arguments to make. | |
84 | ||
85 | Second, the step function, +`+, is abstracted over the actual | |
86 | construction operation, which is created by make, and passed along the | |
87 | fold. This allows the same constant, +`+, to be used for all | |
88 | numeric types. The alternative approach, having a different step | |
89 | function for each numeric type, would be more painful to use. | |
90 | ||
91 | On the surface, it appears that the code checks the digits dynamically | |
92 | to ensure they are valid for the base. However, MLton will simplify | |
93 | everything away at compile time, leaving just the final numeric | |
94 | constant. |