(B) Monomorphism restriction "wins" Bindings that fall under the monomorphism restriction can't be generalised Always generalise over implicit parameters *except* for bindings that fall under the monomorphism restriction Consequences * Inlining isn't valid in general * No unexpected loss of sharing * Simple bindings like z = ?y + 1 accepted (get value of ?y from binding site) (C) Always generalise over implicit parameters Bindings that fall under the monomorphism restriction can't be generalised, EXCEPT for implicit parameters Consequences * Inlining remains valid * Unexpected loss of sharing (from the extra generalisation) * Simple bindings like z = ?y + 1 accepted (get value of ?y from occurrence sites) Discussion ~~~~~~~~~~ None of these choices seems very satisfactory. But at least we should decide which we want to do. It's really not clear what is the Right Thing To Do. If you see z = (x::Int) + ?y would you expect the value of ?y to be got from the *occurrence sites* of 'z', or from the valuue of ?y at the *definition* of 'z'? In the case of function definitions, the answer is clearly the former, but less so in the case of non-fucntion definitions. On the other hand, if we say that we get the value of ?y from the definition site of 'z', then inlining 'z' might change the semantics of the program. Choice (C) really says "the monomorphism restriction doesn't apply to implicit parameters". Which is fine, but remember that every innocent binding 'x = ...' that mentions an implicit parameter in the RHS becomes a *function* of that parameter, called at each use of 'x'. Now, the chances are that there are no intervening 'with' clauses that bind ?y, so a decent compiler should common up all those function calls. So I think I strongly favour (C). Indeed, one could make a similar argument for abolishing the monomorphism restriction altogether. I disagree. I think it's important to have a simple model of how many times expressions are evaluated. Function bodies are clearly evaluated many times, once for each call, but non-function bindings should be evaluated at most once to respect call-by-need semantics. Breaking the monomorphism restriction in ANY case makes both space and time cost of evaluation unpredictable, and brittle when program changes elsewhere introduce or remove an implicit parameter. It isn't good enough to say `the chances are' that a program has, for example, linear time and constant space complexity: the programmer should be able to convince himself of such properties. As far as what one would `expect', it's in the very nature of dynamic binding that it makes the meaning of an expression depend on its context. I for one would certainly not expect that inlining a definition bound to such an expression should preserve its meaning! Inlining changes the context, so `of course' can change the meaning. So I strongly prefer (B)! John Hughes
John Hughes wrote:
I think it's important to have a simple model of how many times expressions are evaluated. Function bodies are clearly evaluated many times, once for each call, but non-function bindings should be evaluated at most once to respect call-by-need semantics.
Maybe I misinterpret the Haskell Report but I thought it does not even demand call-by-need evaluation (it only speaks of non-strict semantics). So why have a special rule in the language definition to support something cbn-ish for this particular case? As long as the Report does not specify any execution model the MR looks rather arbitrary to me.
Breaking the monomorphism restriction in ANY case makes both space and time cost of evaluation unpredictable, and brittle when program changes elsewhere introduce or remove an implicit parameter. It isn't good enough to say `the chances are' that a program has, for example, linear time and constant space complexity: the programmer should be able to convince himself of such properties.
Why isn't it good enough if the compilers give warnings then? The Report could even require it. To me it seems overly restrictive to rule out perfectly correct programs for the sole reason of potentially surprising space/time behaviour. After all it is not forbidden to write Haskell programs with obscure space leaks. -- Andreas Rossberg, rossberg@ps.uni-sb.de "Computer games don't affect kids. If Pac Man affected us as kids, we would all be running around in darkened rooms, munching pills, and listening to repetitive music."
[...many lines deleted...] I think it's important to have a simple model of how many times expressions are evaluated. Function bodies are clearly evaluated many times, once for each call, but non-function bindings should be evaluated at most once to respect call-by-need semantics. Breaking the monomorphism restriction in ANY case makes both space and time cost of evaluation unpredictable, and brittle when program changes elsewhere introduce or remove an implicit parameter. It isn't good enough to say `the chances are' that a program has, for example, linear time and constant space complexity: the programmer should be able to convince himself of such properties.
But a term with an "implicit" argument is a function no matter how you turn it, you just don't write the argument explicitely.
As far as what one would `expect', it's in the very nature of dynamic binding that it makes the meaning of an expression depend on its context. I for one would certainly not expect that inlining a definition bound to such an expression should preserve its meaning! Inlining changes the context, so `of course' can change the meaning. So I strongly prefer (B)!
John Hughes
_______________________________________________ Haskell mailing list Haskell@haskell.org http://www.haskell.org/mailman/listinfo/haskell
John Hughes wrote:
... Function bodies are clearly evaluated many times, once for each call, but non-function bindings should be evaluated at most once to respect call-by-need semantics.
Isn't this a very fragile distinction? It seems so susceptible to routine program transformations by both programmers and compilers (e.g., lambda lifting/unlifting, which can change a non-function finding into a function binding and vice versa). Also, I agree with Andreas Rossberg's observation:
... the Haskell Report ... does not even demand call-by-need evaluation (it only speaks of non-strict semantics).
Nikhil
Wed, 2 May 2001 10:51:58 +0200 (MET DST), John Hughes <rjmh@cs.chalmers.se> pisze:
Breaking the monomorphism restriction in ANY case makes both space and time cost of evaluation unpredictable, and brittle when program changes elsewhere introduce or remove an implicit parameter.
You can always use 'case' instead of 'let' for variable bindings. I would remove the monomorphism restriction, leaving 'let' mostly for function-like bindings, which can be optimized to value bindings in some cases. Perhaps it would be good to introduce strictness annotations in 'let' and 'where'. Clean has 'let!'. It would allow increasing strictness yet more than 'case' does, without the need of transforming 'let' into 'case' or adding `seq`. I would attach '!' to individual variables (or patterns) rather than to 'let', so it's usable in 'where', 'case' and function arguments, with some bindings strict and others lazy. The semantics would insert var `seq` at the start of the appropriate body or rhs for each strict-marked var, in unspecified order (it doesn't matter in what order they are evaluated, it matters that all are evaluated). -- __("< Marcin Kowalczyk * qrczak@knm.org.pl http://qrczak.ids.net.pl/ \__/ ^^ SYGNATURA ZASTÊPCZA QRCZAK
You can always use 'case' instead of 'let' for variable bindings.
Except, of course, for top level bindings which is where the monomorphism restriction is usually most noticable.
I would remove the monomorphism restriction, [...]
This seems to be a pretty common sentiment but I don't see anyone pointing out that the monomorphism restriction is there for _two_ reasons: 1) The performance issues that are currently getting all the attention. 2) Preventing ambiguity in examples like: [(n,s)] = reads t where the value of "s" (whose type is String) is determined by the type of "n". This example comes from the H'98 report, section 4.5.5 "The Monomorphism Restriction". Unless I missed something, reason (2) prevents us from completely getting rid of the restriction even if we are willing to ignore (1). (Though if our only goal was to solve (2), it may be that the rule could be a lot less restrictive.) -- Alastair Reid
Thu, 3 May 2001 15:26:38 -0600, Alastair Reid <reid@cs.utah.edu> pisze:
You can always use 'case' instead of 'let' for variable bindings.
Except, of course, for top level bindings which is where the monomorphism restriction is usually most noticable.
Right, but an explicit monomorphic type signature would ensure that it's computed once. Type signatures on toplevel bindings are a good idea anyway, and there is no ambiguity for implicit parameters which must yield a function-like binding.
Unless I missed something, reason (2) prevents us from completely getting rid of the restriction even if we are willing to ignore (1).
Ok. Let's keep (2) and remove (1). ghc -fno-monomorphism-restriction allows polymorphis cases like (2) and they do create ambiguous types like Read a => String. I would treat it as a bug: removing monomorphism restriction should not go that far. Haskell 98 doesn't allow removing it for pattern bindings by supplying a type signature. Here is a crazy idea: either of these ~x = ... x@_ = ... creates a pattern binding, so it's a way of writing it without a type signature if there was no monomorphic restriction :-) -- __("< Marcin Kowalczyk * qrczak@knm.org.pl http://qrczak.ids.net.pl/ \__/ ^^ SYGNATURA ZASTÊPCZA QRCZAK
participants (6)
-
Alastair Reid -
Andreas Rossberg -
Erik Meijer -
John Hughes -
Marcin 'Qrczak' Kowalczyk -
Rishiyur S. Nikhil