Standard ML does not have built-in support for optional arguments (unlike OCaml). Despite the absence of built-in support, it is easy to emulate optional arguments.
For example, consider the function
fun f x {a, b, c} = a * (real c) + b * (real x)for which the parameters a, b, and c should take on the default values specified by
val defs = {a = 0.0, b = 0.0, c = 0}We wish to provide an (optionalized) function f' and two (general) functions $ and ` such that
val X = f' 1 $ val Y = f' 1 (` #b 1.0) $ val Z = f' 1 (` #a 1.0) (` #c 2) (` #b 1.0) $ val () = print (concat ["X = ", Real.toString X, ", Y = ", Real.toString Y, ", Z = ", Real.toString Z, "\n"])prints out the following:
X = 0, Y = 1, Z = 3
Here is the signature for the two general supporting functions, as well as two additional functions:
signature OPTIONAL = sig type ('upds, 'opts, 'res) t type ('upds, 'opts, 'res, 'k) u = (('upds, 'opts, 'res) t -> 'k) -> 'k val $ : ('upds, 'opts, 'res) t -> 'res val ` : ('upds -> ('opts -> 'x -> 'opts)) -> 'x -> ('upds, 'opts, 'res) t -> ('upds, 'opts, 'res, 'k) u val `` : 'opts -> ('upds, 'opts, 'res) t -> ('upds, 'opts, 'res, 'k) u val make : 'upds -> 'opts -> ('opts -> 'res) -> ('upds, 'opts, 'res, 'k) u endOur intention is that the type ('upds, 'opts, 'res) t represents the type of functions returning the type 'res and whose optional arguments are given by the (record) type 'opts; supporting the optional arguments is the (record) type 'upds of update functions. The function ` introduces an override for an optional argument,while $ marks the end of optional arguments. The function `` provides a convenient way to simultaneously set all optional arguments; it can also be useful when the 'opts type is kept abstract, in which case the defining module may provide values of type 'opts that may be used with `` to install a new set of default values, while ` may continue to be used to override these new defaults. Finally, the make function transforms a function to use optional arguments:
A structure matching OPTIONAL could be used as follows.
functor MakeF (S: OPTIONAL) :> sig type opts type 'a upd = opts -> 'a -> opts type upds = {a: real upd, b: real upd, c: int upd} val f' : int -> (upds, opts, real, 'k) S.u val opts_def2 : opts end = struct open S (* define the function and defaults *) fun f x {a, b, c} = a * (real c) + b * (real x) type opts = {a: real, b: real, c: int} val opts_def1 (* : opts *) = {a = 0.0, b = 0.0, c = 0} val opts_def2 (* : opts *) = {a = 1.0, b = 1.0, c = 1} (* define the update functions *) type 'a upd = opts -> 'a -> opts val upda (* : real upd *) = fn {a, b, c} => fn a' => {a = a', b = b, c = c} val updb (* : real upd *) = fn {a, b, c} => fn b' => {a = a, b = b', c = c} val updc (* : int upd *) = fn {a, b, c} => fn c' => {a = a, b = b, c = c'} type upds = {a: real upd, b: real upd, c: int upd} val upds = {a = upda, b = updb, c = updc} fun f' x (* : (upds, opts, real, 'k) u *) = make upds opts_def1 (f x) end functor TestOptionalF (S: OPTIONAL) = struct structure F = MakeF (S) open F S val X = f' 1 $ val Y = f' 1 (` #b 1.0) $ val Z = f' 1 (` #a 1.0) (` #c 2) (` #b 1.0) $ val () = print (concat ["X = ", Real.toString X, ", Y = ", Real.toString Y, ", Z = ", Real.toString Z, "\n"]) val X = f' 1 (`` opts_def2) $ val Y = f' 1 (`` opts_def2) (` #b 1.0) $ val Z = f' 1 (`` opts_def2) (` #a 1.0) (` #c 2) (` #b 1.0) $ val () = print (concat ["X = ", Real.toString X, ", Y = ", Real.toString Y, ", Z = ", Real.toString Z, "\n"]) end
The implementation of OPTIONAL is actually quite straightforward:
structure Optional :> OPTIONAL = struct type ('upds, 'opts, 'res) t = 'upds * 'opts * ('opts -> 'res) type ('upds, 'opts, 'res, 'k) u = (('upds, 'opts, 'res) t -> 'k) -> 'k val make = fn upds => fn defs => fn f => fn k => k (upds, defs, f) fun `` opts = fn (upds, opts, f) => fn k => k (upds, opts, f) fun ` sel v = fn (upds, opts, f) => fn k => k (upds, sel upds opts v, f) val $ = fn (upds, opts, f) => f opts end
One may also mix sequences of required and optional arguments.
functor MakeG (S: OPTIONAL) :> sig type optsABC type 'x updABC = optsABC -> 'x -> optsABC type updsABC = {a: real updABC, b: real updABC, c: int updABC} type optsDEF type 'x updDEF = optsDEF -> 'x -> optsDEF type updsDEF = {d: int updDEF, e: int updDEF, f: real updDEF} val g' : int -> (updsABC, optsABC, real -> (updsDEF, optsDEF, string -> unit, 'kDEF) S.u, 'kABC) S.u end = struct open S (* define the function and defaults *) fun g x {a, b, c} y {d, e, f} s = let val z1 = a * (real c) + b * (real x) val z2 = (real d) * f + (real e) * y in print (concat [s, Real.toString (z1 + z2), s, "\n"]) end type optsABC = {a: real, b: real, c: int} val optsABC_def (* : optsABC *) = {a = 0.0, b = 0.0, c = 0} type optsDEF = {d: int, e: int, f: real} val optsDEF_def (* : optsDEF *) = {d = 1, e = 1, f = 1.0} (* define the update functions *) type 'a updABC = optsABC -> 'a -> optsABC val upda (* : real updABC *) = fn {a, b, c} => fn a' => {a = a', b = b, c = c} val updb (* : real updABC *) = fn {a, b, c} => fn b' => {a = a, b = b', c = c} val updc (* : int updABC *) = fn {a, b, c} => fn c' => {a = a, b = b, c = c'} type updsABC = {a: real updABC, b: real updABC, c: int updABC} val updsABC = {a = upda, b = updb, c = updc} type 'a updDEF = optsDEF -> 'a -> optsDEF val updd (* : real updDEF *) = fn {d, e, f} => fn d' => {d = d', e = e, f = f} val upde (* : real updDEF *) = fn {d, e, f} => fn e' => {d = d, e = e', f = f} val updf (* : int updDEF *) = fn {d, e, f} => fn f' => {d = d, e = e, f = f'} type updsDEF = {d: int updDEF, e: int updDEF, f: real updDEF} val updsDEF = {d = updd, e = upde, f = updf} val g' (* : (upds, opts, real, 'k) u *) = (fn x => make updsABC optsABC_def (fn optsABC => fn y => make updsDEF optsDEF_def (fn optsDEF => fn s => g x optsABC y optsDEF s))) end functor TestOptionalG (S: OPTIONAL) = struct structure G = MakeG (S) open G S val () = g' 1 (` #a 3.0) $ 1.0 (` #e 2) $ " ** " end
To make a complete program and test the above code, we can apply the TestOptionalF and TestOptionalG functors to our implementation.
structure TestF = TestOptionalF (Optional) structure TestG = TestOptionalG (Optional)
Running the complete code prints out the following.
X = 0, Y = 1, Z = 3 X = 2, Y = 2, Z = 3 ** 3 **
Download
Notes
-
The ability to pass a record selector as a first-class function value is key to the succinctness of this technique.