[MLton] optional arguments and functional record update via fold
Stephen Weeks
MLton@mlton.org
Sat, 3 Sep 2005 15:47:07 -0700
Here is an implementation of optional arguments and functional record
update using fold and product types. The implementation defines a
family of functions, makeOpt<i>, and a single combinator, "O", with
nothing infix (other than product). One can define a function f
taking optional arguments using
fun f $ = makeOpt<i> (p2r, p2r, r2p) (f', r0) $
Here, r0 is a record of the default values of the optional arguments,
f' is the final function to be called on the record, i is the number
of record fields, and (p2r, r2p) is the isomorphism between the
optional-arguments record the corresponding product (just as we used
to use an isomorphism between a record and a tuple). The usual
problem with lack of first-class polymorphism requires us to pass the
additional copy of p2r.
One can call f, supplying values for optional arguments "a" and "b",
like this:
f O#a va O#b vb $
Functional record update is simply optional arguments with an identity
function applied at the end. That is,
fun update r $ = makeOpt<i> (p2r, p2r, r2p) (id, r) $
If updateAB were defined this way, supplying the isomorphism on
records with fields "a" and "b", then we could do
updateAB {a = 13, b = "hello"} O#b "goodbye"$
Because everything is polymorphic in the field values, the same update
function works here too.
updateAB {a = 13.5, b = true} O#b false O#a 12.5$
The thing I most like about from this approach is that the optional
arguments (or record updates) and end of arguments terminator are the
same for every use (and should hence probably be global). The only
thing that changes is the function. This works well and avoids
having to open a DSL or use long names for each optional argument.
New in this approach too is the use of products allowing concise
definition of the internal update functions.
----------------------------------------------------------------------
datatype ('a, 'b) product = & of 'a * 'b
infix &
fun $ (a, f) = f a
fun curry f x y = f (x, y)
fun id x = x
fun pass x f = f x
structure Fold =
struct
val fold = pass
fun step0 h (a1, f) = fold (h a1, f)
fun step1 h $ a1 = step0 (curry h a1) $
fun step2 h $ a1 a2 = step0 (fn x => h (a1, a2, x)) $
end
local
datatype ('a, 'b) u = A of 'a | B of 'b
fun next (p, up) =
(fn f => p (f o A) & f o B,
fn (a & b, u) =>
(case u of A u => up (a, u) | _ => a)
& (case u of B b => b | _ => b))
fun n1 () = (fn f => f o A, fn (a, u) => case u of A a => a | _ => a)
fun n2 () = next (n1 ())
fun n3 () = next (n2 ())
fun n4 () = next (n3 ())
fun make n =
let
val (p, up) = n ()
in
fn (p2r, p2r', r2p) =>
fn (f, r0) =>
Fold.fold ((p2r' (p id), up, r2p r0), fn (_, _, p) => f (p2r p))
end
in
fun makeOpt2 $ = make n2 $
fun makeOpt3 $ = make n3 $
fun makeOpt4 $ = make n4 $
end
fun O $ = Fold.step2 (fn (s, v, (r, up, p)) => (r, up, up (p, s r v))) $
fun makeOptAB $ =
let
fun p2r (v1 & v2) = {a = v1, b = v2}
fun r2p {a = v1, b = v2} = (v1 & v2)
in
makeOpt2 (p2r, p2r, r2p) $
end
fun makeOptBCD $ =
let
fun p2r (v1 & v2 & v3) = {b = v1, c = v2, d = v3}
fun r2p {b = v1, c = v2, d = v3} = (v1 & v2 & v3)
in
makeOpt3 (p2r, p2r, r2p) $
end
fun updateAB r = makeOptAB (id, r)
fun updateBCD r = makeOptBCD (id, r)
val _ = updateAB {a = 13, b = "hello"} O#b "goodbye"$
val _ = updateAB {a = 13.5, b = true} O#b false O#a 12.5$
val _ = updateBCD {b = 1, c = 2, d = 3} O#c 4 O#c 5$
fun f1 $ = makeOptAB (fn {a, b} => print (concat [Int.toString a, " ",
Real.toString b, "\n"]),
{a = 0, b = 0.0}) $
fun f2 $ =
makeOptBCD (fn {b, c, d} =>
print (concat [Int.toString b, " ",
Real.toString c, " ",
d, "\n"]),
{b = 0, c = 0.0, d = "<>"}) $
val () = f1 $
val () = f1 O#a 13 O#a 12 $
val () = f1 O#a 13 O#b 17.5 $
val () = f2 $
val () = f2 O#d "goodbye" $
val () = f2 O#d "hello" O#b 17 O#c 19.3 $