[MLton-user] curried function definition and pattern destructuring

Stephen Weeks sweeks@sweeks.com
Mon, 17 Jul 2006 18:49:08 -0700


I hit a subtle bug in some SML code I was recently writing.  The
essence of the bug is that I had defined a function as follows:

  fun f (T {x, ...}) y = e

Here, 'T' is a constructor for some datatype and 'e' is an expression
that refers to 'x' and 'y'.  I had intended for 'f' to be applied to a
value of the datatype and to extract the 'x' field of the record,
building a closure closed only over 'x', not the entire record.  This
was done to avoid a space leak, as the closure might be long lived,
and other fields in the record could take an unbounded amount space
and might be dead.

Unfortunately, the Definition of SML specifies that the above
definition of 'f' is equivalent to:

  fun f z y = let T {x, ...} = z in e end

That is, the destructuring of the record is not done until all of the
curried arguments are supplied.  Thus, the entire record was held and
there was a space leak.

The bug was easily fixed by changing the definition of 'f' to:

  fun f (T {x, ...}) = fn y => e

This destructures the record as soon as 'f' is supplied with its first
argument, as needed.

It is arguably a design mistake that the Definition does what it does;
but, since it does, it useful to keep in mind that these two
definitions of 'f' are not equivalent.

  fun f (T {x, ...}) y = e
  fun f (T {x, ...}) = fn y => e