Not sure why this pattern guard matches
Asked Answered
S

1

5

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) ?

Strict answered 16/12, 2014 at 18:10 Comment(1)
mulId is a new local variable, unrelated to the previously defined one. You want Lit w <- x , w==mulId = ... instead.Unshroud
U
9

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.

Unshroud answered 16/12, 2014 at 18:43 Comment(3)
Thanks! On the last note, "you can also simplify" - on what basis is it simpler? only in that it does not use pattern guards? or, perhaps it is more idiomatic to avoid pattern guards if possible?Strict
@Strict Pattern guards are a GHC extension of Haskell, which is useful when you need to write e.g. f x y | Just z <- g (x+y) = ... where there's a complex expression on the right of <-. When using instead pattern <- x, the standard x@pattern construct suffices. The latter is also more idiomatic. Which one is actually simpler is of course a matter of taste.Unshroud
thx, is there any particular reason why you used x@.. even though not required? squashMul x@(Lit w) y | w == mulId = y => squashMul (Lit w) y | w == mulId = yStrict

© 2022 - 2024 — McMap. All rights reserved.