Pattern guards

Yitzchak Gale gale at
Thu Sep 28 09:40:30 EDT 2006

I would like to suggest a correction to ticket #56,
"Pattern Guards".

It is easy to show that every expression written
using pattern guards can also be written in
Haskell 98 in a way that is essentially
equivalent in simplicity. (Proof below.)

In my opinion, the Haskell 98 version below is
more clear than the pattern guard version - it
makes the monad explicit. Even if you disagree,
I think it would be very difficult to argue that the
difference is important enough to justify the extreme
measure of adding new syntax to the language.

Therefore, the first two items under "Pros" are
false, and should be removed. The only remaining
"Pro" is that the extension is well-specified, which
has no value on its own.

The purpose of Haskell' is to remove warts from
Haskell, not add new ones. Pattern guards are
a serious wart - they further overload syntax that
is arguably already overused, as pointed out
in the referenced paper by Martin Erwig and
Simon Peyton Jones [EPJ].

I hope that there is still time to retract the evil decree
of "definitely in" Proposal Status for this ticket.


Proof: We first assume that the following declarations
are available, presumably from a library:

> data Exit e a = Continue a | Exit {runExit :: e}
> instance Monad (Exit e) where
>   return = Continue
>   Continue x >>= f = f x
>   Exit e >>= _ = Exit e

(Note that this is essentially the same as the Monad
instance for Either defined in Control.Monad.Error,
except without the restriction that e be an instance
of Error.)

> maybeExit :: Maybe e -> Exit e ()
> maybeExit = maybe (return ()) Exit

Now given any function binding using pattern guards:

 | qual11, qual12, ..., qual1n = exp1
 | qual21, qual22, ..., qual2n = exp2

we translate the function binding into Haskell 98 as:

funlhs = runExit $ do
  maybeExit $ do {qual11'; qual12'; ...; qual1n'; return (exp1)}
  maybeExit $ do {qual21'; qual22'; ...; qual2n'; return (exp2)}


  qualij' -> pat <- return (e) if qualij is pat <- e
  qualij' -> guard (qualij) if qualij is a boolean expression
  qualij' -> qualij if qualij is a let expression

For a conventional guard:

 | p = exp

we can simplify the translation to:

  when (p) $ Exit (exp)

Simplifications are also possible for other special cases.

This concludes the proof. Here are some examples, taken
from [EPJ]:

> clunky env var1 var2
>  | Just val1 <- lookup env var1
>  , Just val2 <- lookup env var2
>  = val1 + val2
>  ...other equations for clunky

translates to:

> clunky env var1 var2 = runExit $ do
>   maybeExit $ do
>     val1 <- lookup env var1
>     val2 <- lookup env var2
>     return (val1 + val2)
>  ...other equations for clunky

> filtSeq :: (a->Bool) -> Seq a -> Seq a
> filtSeq p xs
>  | Just (y,ys) <- lview xs, p y = lcons y (filtSeq p ys)
>  | Just (y,ys) <- lview xs      = filtSeq p ys
>  | otherwise                    = nil

translates to:

> filtSeq :: (a->Bool) -> Seq a -> Seq a
> filtSeq p xs = runExit $ do
>   maybeExit $ do
>     (y,ys) <- lview xs
>     guard $ p y
>     return $ lcons y $ filtSeq p ys
>   maybeExit $ do
>     (y,ys) <- lview xs
>     return $ filtSeq p ys
>   Exit nil

Note that in this case, the Maybe monad alone is
sufficient. That eliminates both the double lookup and
the double pattern match, as discussed in [EPJ]:

> filtSeq :: (a->Bool) -> Seq a -> Seq a
> filtSeq p xs = fromMaybe nil $ do
>   (y,ys) <- lview xs
>   return $ if (p y)
>     then lcons y (filtSeq p ys)
>     else filtSeq p ys

More information about the Haskell-prime mailing list