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

- optionalargs.sml

## Notes

- The ability to pass a record selector as a first-class function value is key to the succinctness of this technique.

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