Just wanted to add two minor points to the discussion. Beforehand let
me say that I *really* like Simon's proposal [despite the various
objections]. Mainly, because it's a small upwards compatible change with
a considerable gain.
Going back to Simon's first `clunky' example.
> Now consider the following definition:
>
> clunky env var1 var2 | ok1 && ok2 = val1 + val2
> | otherwise = var1 + var2
> where
> m1 = lookup env var1
> m2 = lookup env var2
> ok1 = maybeToBool m1
> ok2 = maybeToBool m2
> val1 = expectJust m1
> val2 = expectJust m2
As a (partial) solution I would suggest to use a simple case expression
involving pairs. [I have not seen this one before, but in my humble
opinion it is the most natural solution in 1.4].
clunky env var1 var2 = case (lookup env var1, lookup env var2) of
(Just val1, Just val2) -> val1 + val2
_ -> ...
It scales up nicely when more than three lookups are involved. Of
course, it does not work if `clunky' consists of more than one equation
and the one above is expected to `fail'. [It is debatable, however,
whether it is good programming style to use failing equations. After
all that's what we tell our students: Use whenever possible disjunct
and exhaustive patterns to separate cases.]
The second point concerns nested guard expressions which I find rather
attractive for the following reason: guards allow to shift nested
conditionals from the rhs to the lhs of an equation. Think of applying
the following transformation:
lhs = if b1 then e1
else if b2 then e2
...
else if bn then en
else e
-->
lhs | b1 = e1
| b2 = e2
...
| bn = en
| otherwise = e
But it may well be the case that one of the ei's also involves an
if-then-else conditonal which I would like to get rid of using the
above transformation: The result are nested guards on the left hand
side. Currently, I must mix guards and top-level if-then-elses which
I'm not particularly fond of.
Here is a real world ;-) example taken almost verbatim from
ghc'c FiniteMap module which implements Adam's balanced trees.
mkBalBranch l a r
| sl + sr < 2 = mkBranch l a r
| sl*ratio < sr = case r of
Branch _ rl _ rr
| size rl < 2 * size rr -> singleL l a r
| otherwise -> doubleL l a r
| sr*ratio < sl = case l of
Branch _ ll _ lr
| size lr < 2 * size ll -> singleR l a r
| otherwise -> doubleR l a r
| otherwise = mkBranch l a r
where sl = size l
sr = size r
Note the use of case expression to handle the subcases; for that
purpose irrefutable patterns were once invented. Here we are:
mkBalBranch l@(~Branch _ rl _ rr) a r@(~Branch _ ll _ lr)
but now we are forced to use an if-then-else for both of the
subcases. Using nested guards we arrive at
mkBalBranch l@(~Branch _ rl _ rr) a r@(~Branch _ ll _ lr)
| sl + sr < 2 = mkBranch l a r
| sl*ratio < sr
| size rl < 2 * size rr = singleL l a r
| otherwise = doubleL l a r
| sr*ratio < sl
| size lr < 2 * size ll = singleR l a r
| otherwise = doubleR l a r
| otherwise = mkBranch l a r
where sl = size l
sr = size r
which exposes the different cases quite clearly.
Regards, Ralf