MLton 20051202 OptionalArguments
Home  Index  
Optional arguments are function parameters which may be omitted from applications of the function, in which case the parameters take on default values.

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


Last edited on 2005-12-02 04:23:32 by StephenWeeks.