[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: Pattern guards.
Original-Via: uk.ac.nsf; Tue, 9 Oct 90 14:09:33 BST
From: Kent Karlsson <kent>
Date: Tue, 9 Oct 90 13:59:07 +0100
To: haskell@cs.yale.edu
Subject: Re: Pattern guards.
Original-Sender: kent%se.chalmers.cs@yale.edu
Sender: haskell-request@cs.glasgow.ac.uk
> accommodate more expressive guards, but it is certainly a topic worth
> pursuing for Haskell 2. I suppose that's what you're doing?
Yes.
> The report does not say whether duplicate identifiers in patterns
> are allowed or not,
>
> The Report says clearly that they are NOT allowed -- patterns must be
> "linear".
OK, I missed that...
>
> Making things smaller does *not* necessarily make it simpler.
> (Otherwise we would probably write our programs as bit-patterns in
> some standardized machine code... :-) The system must have integrity
> for it to have simplicity. If you make cutaways that destroy the
> integrity, then the result will inevitably be complicated.
>
> Lots of philosophy here...:-)
(So intended :-)
>
> OK, here's a nice simple property:
>
> Argument order may always be changed without changing the scope of ids.
>
> This property holds under the current proposal, but not under yours.
> Seems a shame.
1. Yes, but I still would not recommend a programmer to use a guard
"in the middle" of a pattern unless it's "necessary".
2. Under my (hinted) proposal it's best to make all variables bound by
a pattern "unbound" at the start of the pattern, thus catching
any scope problems as static errors.
3. My proposal allows for a simple and straightforward explanation of
the patterns in e.g. "f 0 0 = ...". And since duplicate identifiers
are not allowed in patterns, it's still easy *in my proposal* to write
their equivalent. If that cannot easily be done in Haskell currently,
then that seems a shame...
4. Here is another nice simple property:
Argument order may always be changed without changing the strictness
properties.
This is not true in the current proposal... (nor in mine).
>
> > than to keep things simple, the chief objection being the following:
> > x = 1
> > f y | (x>1) x = x+y
> > Does the x in the guard refer to f's formal parameter or the outer x?
>
> It cannot possibly refer to f's second formal parameter, it has not been
> bound yet!
>
> yes, by YOUR definition of "bound".
>
> It can refer to the outer x, or it can (better yet) be illegal.
> This is perhaps a bit clearer when using another example:
> f y | (x>1) (SOME_CONSTRUCTOR x) = x+y
> The pattern for the second argument must not be matched before the
> evaluation of the guard ...
>
> Why not? (I'm not saying it should, just asking for the rationale
> behind your decision.)
Because that may change the strictness properties of some function
definitions. E.g. (quoting my first note on this matter):
| h 0 (C1 x) = ...
| ===
| h 0 (C1 x) = ...
| ===
| h y | (y == fromInteger 0) (C1 x) = ...
|
| If the guard was to be moved to the end:
|
| h y (C1 x) | (y == fromInteger 0) = ...
|
| the "pattern matching" would be done "from right to left" in this example,
| if it was the "h 0 (C1 x) = ..." clause that was transformed into
| "h y (C1 x) | (y == fromInteger 0) = ...", whereas Haskell specifies that
| it should be done from left to right, which is still the case with
| "h y | (y == fromInteger 0) (C1 x) = ..."
Evaluating a guard "where it stands" would also be what a neophite expects.
> For example, consider:
>
> f ~(SOME_CONSTRUCTOR x) | x>1 = ...
>
> Here the match was "lazy" but the guard forced it to be "strict" -- so
> already one must understand the subtleties of what gets matched when.
1. I think ~-patterns are a hack, and I try to avoid them.
2. But if you can give *easy* translations of patterns with numerals, e.g.
"f 0 0 = ...", using ~-patterns, and *easily* write the equivalent
of duplicate identifiers in a pattern using ~-patterns, etc., (****)
then so be it, and I will withdraw my proposal. (My only goal
is simplicity, believe it or not! :-)
>
> In general a guard can only depend upon things that are bound where
> the guard occurs.
>
> Of course; but again I ask: by what definition of bound?
Informally:
A variable in a guard in an "lhs" (or "pat") part of a
clause (or case entry) is bound if
- it's bound outside of the "lhs" (or "pat") and not (re)bound
*by* the "lhs" (or "pat") nor *by* a where-definition attached
to the clause (or case entry), or
- it's bound *by* the "lhs" (or "pat") *"to the left of"* the guard, or
- it's bound *by* a where-definition *attached to the clause (or
case entry)* *and* its definition only depends upon variables
bound in the guard (note the greatest fixed point assumed here), or
- it's bound by a where-definition attached to (the appropriate part
of) the guard. (Recall that a guard is an expression...)
Remarks:
* Under no circumstances would I recommend a programmer to attach a
where-definition directly to (a part of) a guard!
* Only a small part of this is unnecessary if you succeed with (****)
above. :-)
* The 'and not (re)bound *by* the "lhs"...'-part is there in order
to catch scope errors statically.
* the variables bound *in* a where-definition are:
- the variables bound outside of the where-definition (though some of them
may be *re*bound by the where-definition)
- the variables bound *by* the where-definition
- the variables bound by a where-definition attached to (the appropriate
part of) the expressions in the where-definition.
(but this is as it used to be.)
* E.g. in "g x | G1 y | G2 z | G3 = ..."
x is available in G1, x and y in G2, and x, y, and z in G3.
* This is in accordance with the rule that pattern matching is
done from left to right in Haskell. This binding rule is probably
what a neophite would expect, having heard the pattern matching rule...
* For clarity maybe parenthesis should be used (esp. if guards occur inside
patterns...): "g (x | G1) (y | G2) (z | G3) = ..."
But you maybe have some grammars for this in stock...
>
> This would hold also if there is a where-definition
> attached to the clause ((as opposed to just the expression...)). So, in
> g x | (G1) y | (G2) = ...
> where z1 = ...y...
> z2 = ...x... -- no occurence of y, nor of z1
> z1 may not occur in G1
>
> Why not?
Since y is still unbound in G1, and the definition of z1 depends upon y!
> Are you proposing that a static error should result?
Yes.
>
> (since y is still unbound; *of course* it should *not* refer
> to an outer y),
>
> Why not?
If y in the definition of z1 could refer to an outer y, then z1
would have different meanings in G1 and G2 (since in G2, y has been bound),
I'm sure that that would be *very* unexpected!
>
> but it may occur in G2 (since y has then been bound).
> z2 may occur in both G1 and G2 though.
>
> Your proposal is sounding more and more complex every minute.
Yes, we're getting into the same problems as with comments:
The simple solution *looks* much more complicated than
the very complicated solutions (which *look* very simple)! (:-!)
(You can still choose the really simple solution:
- (Repeated variables are already banned.)
- Ban "n+k"-patterns.
- Ban numeral patterns... :-)
> It was for reasons such as these that we chose the current design.
>
> (Hopefully it's clear that I'm just playing devil's advocate here:
> tell me what the binding rules are, and convince me that they are
> simple enough for the neophite. If you succeed I may support the
> proposal for Haskell 2.)
>
> -Paul
/kent k