Import Upstream version 20180207
[hcoop/debian/mlton.git] / doc / guide / src / VariableArityPolymorphism.adoc
CommitLineData
7f918cf1
CE
1VariableArityPolymorphism
2=========================
3
4<:StandardML:Standard ML> programmers often face the problem of how to
5provide a variable-arity polymorphic function. For example, suppose
6one is defining a combinator library, e.g. for parsing or pickling.
7The signature for such a library might look something like the
8following.
9
10[source,sml]
11----
12signature COMBINATOR =
13 sig
14 type 'a t
15
16 val int: int t
17 val real: real t
18 val string: string t
19 val unit: unit t
20 val tuple2: 'a1 t * 'a2 t -> ('a1 * 'a2) t
21 val tuple3: 'a1 t * 'a2 t * 'a3 t -> ('a1 * 'a2 * 'a3) t
22 val tuple4: 'a1 t * 'a2 t * 'a3 t * 'a4 t
23 -> ('a1 * 'a2 * 'a3 * 'a4) t
24 ...
25 end
26----
27
28The question is how to define a variable-arity tuple combinator.
29Traditionally, the only way to take a variable number of arguments in
30SML is to put the arguments in a list (or vector) and pass that. So,
31one might define a tuple combinator with the following signature.
32[source,sml]
33----
34val tupleN: 'a list -> 'a list t
35----
36
37The problem with this approach is that as soon as one places values in
38a list, they must all have the same type. So, programmers often take
39an alternative approach, and define a family of `tuple<N>` functions,
40as we see in the `COMBINATOR` signature above.
41
42The family-of-functions approach is ugly for many reasons. First, it
43clutters the signature with a number of functions when there should
44really only be one. Second, it is _closed_, in that there are a fixed
45number of tuple combinators in the interface, and should a client need
46a combinator for a large tuple, he is out of luck. Third, this
47approach often requires a lot of duplicate code in the implementation
48of the combinators.
49
50Fortunately, using <:Fold01N:> and <:ProductType:products>, one can
51provide an interface and implementation that solves all these
52problems. Here is a simple pickling module that converts values to
53strings.
54[source,sml]
55----
56structure Pickler =
57 struct
58 type 'a t = 'a -> string
59
60 val unit = fn () => ""
61
62 val int = Int.toString
63
64 val real = Real.toString
65
66 val string = id
67
68 type 'a accum = 'a * string list -> string list
69
70 val tuple =
71 fn z =>
72 Fold01N.fold
73 {finish = fn ps => fn x => concat (rev (ps (x, []))),
74 start = fn p => fn (x, l) => p x :: l,
75 zero = unit}
76 z
77
78 val ` =
79 fn z =>
80 Fold01N.step1
81 {combine = (fn (p, p') => fn (x & x', l) => p' x' :: "," :: p (x, l))}
82 z
83 end
84----
85
86If one has `n` picklers of types
87[source,sml]
88----
89val p1: a1 Pickler.t
90val p2: a2 Pickler.t
91...
92val pn: an Pickler.t
93----
94then one can construct a pickler for n-ary products as follows.
95[source,sml]
96----
97tuple `p1 `p2 ... `pn $ : (a1 & a2 & ... & an) Pickler.t
98----
99
100For example, with `Pickler` in scope, one can prove the following
101equations.
102[source,sml]
103----
104"" = tuple $ ()
105"1" = tuple `int $ 1
106"1,2.0" = tuple `int `real $ (1 & 2.0)
107"1,2.0,three" = tuple `int `real `string $ (1 & 2.0 & "three")
108----
109
110Here is the signature for `Pickler`. It shows why the `accum` type is
111useful.
112[source,sml]
113----
114signature PICKLER =
115 sig
116 type 'a t
117
118 val int: int t
119 val real: real t
120 val string: string t
121 val unit: unit t
122
123 type 'a accum
124 val ` : ('a accum, 'b t, ('a, 'b) prod accum,
125 'z1, 'z2, 'z3, 'z4, 'z5, 'z6, 'z7) Fold01N.step1
126 val tuple: ('a t, 'a accum, 'b accum, 'b t, unit t,
127 'z1, 'z2, 'z3, 'z4, 'z5) Fold01N.t
128 end
129
130structure Pickler: PICKLER = Pickler
131----