smlforward-declarationmutual-recursion

Declaring interdependent values and functions in Standard ML


How do you define functions and values depending on each other in Standard ML?

The following program:

val cmds = [("help", cmd_help)];

fun cmd_help () = List.app (fn cmd => print (#1 cmd ^ "\n")) cmds;

cmd_help ();

Does not compile:

$ mlton example.sml
Error: example.sml 1.22-1.29.
  Undefined variable: cmd_help.

Is it possible to define cmds and cmd_help such that they both know about each other and are also exposed as top-level definitions to the rest of the program?


Solution

  • Interesting question, the most obvious way would be to make cmds a unitary function, and then call that in cmd_help just like a normal pair of recursive functions.

    fun cmds () = [("help", cmd_help)]
    and cmd_help () = List.app (fn cmd => print ((#1 cmd) ^ "\n")) (cmds ());
    cmd_help ();
    

    One would expect that you could reverse the situation, treating cmds_help as a value. Indeed the syntax even appears to parse.

    val a = ()
    and b = fn () => ()
    

    However, it doesn't actually compile when you add mutual recursion into the works (I tried the following with MLton and smlnj). Manually declaring types because the inference wasn't doing particularly well.

    val cmds_v : (string * (unit -> unit)) list = [("help", cmd_help_v)]
    and cmd_help_v : unit -> unit = fn () => List.app (fn (cmd: string * (unit -> unit)) => print ((#1 cmd) ^ "\n")) (cmds_v);
    

    This last case results in:

    Error: unbound variable or constructor: cmds_v

    Error: unbound variable or constructor: cmd_help_v

    So the question now is, why doesn't this work If we look at the section Value Bindings, on pg 42 of The Definition of Standard ML (Revised) in the footnote 25 it says

    (25) When the option is present we have Dom VE ∩ Dom VE′ = ∅ by the syntactic restrictions.

    So while and for functions allows mutual recursion, and for values allows ensures that the values environments are disjoint.

    Alas, I don't know how to do this without promoting a value to a function.