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
end
Our 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.