[MLton-user] currying = staging (was: Fixpoints)

Stephen Weeks sweeks@sweeks.com
Fri, 4 Aug 2006 00:37:45 -0700


> >   1. Only use currying if there is actual staging of computation going
> >      on.
> 
> Ok, I'll bite. :)

Excellent :-).

I pretty much agree with your observations about currying: it
sometimes leads to simpler types and easier to combine functions and
it can help to see the code at a higher level of abstraction.  It can
lead to cute and concise code.  And, as there is no performance
difference between currying and tupling in MLton, that plays no role
in deciding whether to use one or the other.  I also agree that the
lack of staging in Fix.fix is not particularly helpful in
understanding the fixpoint framework.

Nevertheless, I still come to the same conclusion I did before.  Why?
Because I am concerned about development of large programs and
collections libraries.  And there, consistency is a hugely important
concern, and essential to making code easier to develop and easier to
use.  By adopting the rule that currying is only used if staging is
going on (among other rules), a developer has fewer details to
remember and is more quickly able to use a new library or part of a
large program developed by others.  The developer instantly knows how
to call the function.  Furthermore, if a function is curried, the
developer is instantly alerted, solely by the type, that some kind of
staging is going on.  It's not always useful, but it often is, and if
one isn't consistent, one can never convey that useful information.

The rule is also useful in cutting down the design space when
developing a new library.  There is a simple rule, based only on the
implementation, as to what to do.  The developer does not have to try
to guess whether currying will improve concision enough in putative
use cases, possibly switching back and forth as more and more cases
are explored.

I don't buy the argument that staging is purely an operational concern
and that one should write things curried so that one can decide about
staging later (and then use grep to visit uses that may need to
change).  In the cases where staging is actually used in an
implementation it is usually very important that the client understand
the staging and use it correctly.  As a recent example see the
Queue.enque function that I used in my Event structure in a curried
manner in order to avoid a space leak.  By reflecting the important
operational property of staging in the type, one gets the help from
the type system both in developing code and when code changes by
introducing staging.  And again, this only works if one is slavishly
consistent.  As soon as currying only sometimes means staging, then
the developer is forced to look at the implementation (or possibly out
of date or non-existent comments) and will be that much more likely to
misuse a truly staged function.

Another subtle problem with currying in a language with side effects
is that the evaluation of a function and its arguments are
interleaved.  For example, in

  f e1 e2

e1 will be evaluated first, then the first stage of f will be applied,
then e2 will be evaluated, then the second stage of f will be applied.
With tupled arguments, one knows that all the arguments are evaluated
before applying the function.

  f (e1, e2)

If currying always means staging, then one can be sure that this
interleaving is happening.  If currying only sometimes means staging,
then one isn't sure, and either has to add some clumsy syntax to force
evaluation order, or be subject to the staging changing behind one's
back while the type remains the same.