Everybody agrees the monomorphism restriction is a pain:
* Often we WANT to make overloaded definitions of the form variable = expr
* The eta-expansion fix is ugly, and only works if the variable has a
function type
* Adding a type signature instead is tedious during prototyping, and moreover
makes the program less robust against changes to types: a change to a type
elsewhere may invalidate many type signatures, even though it does not
invalidate the associated definitions.
On the other hand, interpreting such definitions using call-by-name when the
programmer expects call-by-need would REALLY introduce a trap for the unwary.
Some suggest that it is enough for compilers to issue a warning when using
call-by-name. I disagree strongly. Such a warning may alert the programmer
at the time the overloaded definition is compiled. But programmers need to
understand programs at other times also. The person reading through the code
of a library, for example, trying to understand why a program using that
library is so slow or uses so much memory, will not be helped by warnings
issued when the library was compiled. The distinction between call-by-need
and call-by-name is vital for understanding programs operationally, and it
should be visible in the source.
So, let's make it visible, in the simplest possible way. Let there be TWO
forms of binding: x = e, and x := e (say). A binding of the form `x = e' is
interpreted using call-by-name, and may of course be overloaded: it makes `x'
and `e' exactly equivalent. A binding of the form `x := e' is interpreted
using call-by-need, and is monomorphic; `x' behaves as though it were
lambda-bound. Now, for example,
pi = 4*arcsin 1
is an overloaded definition which (visibly) risks duplicated computation,
while
pi := 4*arcsin 1
is a monomorphic definition at a particular instance which (visibly) does not.
Advantages of this idea over the existing MR:
* Monomorphism is decoupled from the syntactic form of the definition. There
is no need to `eta-convert' definitions to get them into a form that the MR
does not apply to.
* Monomorphism is decoupled from overloading. With this proposal, x := e is
always a monomorphic definition, whether the type of e is overloaded or not.
Thus small changes to e cannot suddenly bring the MR into effect, perhaps
invalidating many uses of x.
* Monomorphism is decoupled from type inference. One may leave the type of
a variable to be inferred regardless of whether it is bound by name or by
need.
Disadvantages:
* Requires another reserved symbol.
* When converting Haskell 1.x to Haskell 2, many := would need to be inserted.
Failure to do so could make programs much less efficient. An (optional)
compiler warning could help here.
An alternative design would be to use := to indicate polymorphism/overloading
in pattern bindings, but retain = for polymorphic function bindings. That
would make conversion from Haskell 1 to Haskell 2 simpler (one would simply
replace = by := in pattern bindings with an overloaded type signature), but is
an unattractively inconsistent design.
John Hughes