Search code examples
haskellpattern-guards

Not sure why this pattern guard matches


Learning Haskell and I am not sure why I don't get the expected result, given these definitions:

instance Ring Integer where
  addId  = 0
  addInv = negate
  mulId  = 1

  add = (+)
  mul = (*)

class Ring a where
  addId  :: a            -- additive identity
  addInv :: a -> a       -- additive inverse
  mulId  :: a            -- multiplicative identity

  add :: a -> a -> a     -- addition
  mul :: a -> a -> a     -- multiplication

I wrote this function

squashMul :: (Ring a) => RingExpr a -> RingExpr a -> RingExpr a
squashMul x y
  | (Lit mulId) <- x = y
  | (Lit mulId) <- y = x
squashMul x y = Mul x y

However:

*HW05> squashMul (Lit 5) (Lit 1)
Lit 1

If I write one version specifically for Integer:

squashMulInt :: RingExpr Integer -> RingExpr Integer -> RingExpr Integer
squashMulInt x y
  | (Lit 1) <- x = y
  | (Lit 1) <- y = x
squashMulInt x y = Mul x y

Then I get the expected result.

Why does (Lit mulId) <- x match even when x is not (Lit 1) ?


Solution

  • Variables used in pattern matching are considered to be local variables. Consider this definition for computing the length of a list:

    len (x:xs) = 1 + len xs
    len _      = 0
    

    Variables x and xs are local variables to this definition. In particular, if we add a definition for a top-level variable, as in

    x = 10
    len (x:xs) = 1 + len xs
    len _      = 0
    

    this does not affect the meaning for len. More in detail, the first pattern (x:xs) is not equivalent to (10:xs). If it were interpreted in that way, we would now have len [5,6] == 0, breaking the previous code! Fortunately, the semantics of pattern matching is robust to such new declarations as x=10.

    Your code

    squashMul :: (Ring a) => RingExpr a -> RingExpr a -> RingExpr a
    squashMul x y
      | (Lit mulId) <- x = y
      | (Lit mulId) <- y = x
    squashMul x y = Mul x y
    

    actually means

    squashMul :: (Ring a) => RingExpr a -> RingExpr a -> RingExpr a
    squashMul x y
      | (Lit w) <- x = y
      | (Lit w) <- y = x
    squashMul x y = Mul x y
    

    which is wrong, since w can be arbitrary. What you want is probably:

    squashMul :: (Eq a, Ring a) => RingExpr a -> RingExpr a -> RingExpr a
    squashMul x y
      | (Lit w) <- x , w == mulId = y
      | (Lit w) <- y , w == mulId = x
    squashMul x y = Mul x y
    

    (The Eq a constraint may depend on the definition of RingExpr, which was not posted)

    You can also simplify everything to:

    squashMul :: (Eq a, Ring a) => RingExpr a -> RingExpr a -> RingExpr a
    squashMul x@(Lit w) y         | w == mulId = y
    squashMul x         y@(Lit w) | w == mulId = x
    squashMul x         y                      = Mul x y
    

    or even to:

    squashMul :: (Eq a, Ring a) => RingExpr a -> RingExpr a -> RingExpr a
    squashMul (Lit w) y       | w == mulId = y
    squashMul x       (Lit w) | w == mulId = x
    squashMul x       y                    = Mul x y
    

    This version does not even use pattern guards, since there's no need to.