6

Example forbidden code (which I would like to be able to write):

isWaiting :: Eq a => a -> PriorityQueue a -> Bool
isWaiting x EmptyQueue = False
isWaiting x (Push x y p) = True 
isWaiting x (Push z y p) = isWaiting x p 

The same logic, but working variant:

isWaiting :: Eq a => a -> PriorityQueue a -> Bool
isWaiting x EmptyQueue = False
isWaiting x (Push z y p) = if x == z then True else isWaiting x p
mushishi
  • 141
  • 1
  • 10
  • I'm not sure what you mean by "linear", or what's wrong with the second (correct) definition. (Note that you can use guards instead of `if/then/else` to get it closer to the first example.) But the first one doesn't work because Haskell pattern matching doesn't work like that, using the same variable name in 2 different patterns doesn't allow the compiler to realise you only want the pattern to apply when those 2 things are equal. – Robin Zigmond May 06 '19 at 09:24
  • @RobinZigmond "Linear", meaning each variable can be mentioned only once (cf linear types). – chepner May 06 '19 at 12:52
  • 2
    Why not sidestep the whole issue? `isWaiting = any . (==)`. GHC can provide you with a `Foldable` instance via deriving if you haven't got one already. – Daniel Wagner May 06 '19 at 14:03
  • 3
    @DanielWagner: Or even `isWaiting = elem`. :-D – sshine May 06 '19 at 14:47
  • 1
    Technically, they aren't linear - you can mention previous variables in the expression part of a view pattern. :) – Alec May 07 '19 at 04:30

2 Answers2

16

Handling non-linear patterns would require to decide equality on the two terms which are being matched. In general, we can't do this:

areFunctionsEqual :: (Integer->Integer) -> (Integer->Integer) -> Bool
areFunctionsEqual f f = True
areFunctionsEqual _ _ = False

The above can not really be allowed since we can't compare functions.

One might however wonder why that is not allowed for types in the Eq class, where decidability is not an issue. That would allow one to write

foo x y x = ...

instead of

foo x y z | z==x = ...

This is harder to justify. One might argue that the first non linear pattern might be written by accident, and introduce subtle bugs. The second is not that longer, and better documents the intent.

Whether this is a good argument or not is a matter of personal opinion, I think.


Another subtle argument:

foo x y z | z==x = bar x

is denotationally equivalent to

foo x y z | z==x = bar z

but the two variants might still lead to different memory footprints, since in a larger program the first one might allow z to be garbage collected, while the second one would allow x to be garbage collected. If, say, z is already referred to elsewhere in the program, we want to use the second form, so that x is garbage collected. The first form would lead to both x and z to be kept in memory.

If we could write foo x y x = bar x, which is going to be garbage collected? Not so clear.

This is arguably a very a minor point, since one could still use the explicit variant, if controlling garbage collection is that important.

chi
  • 111,837
  • 3
  • 133
  • 218
  • Assuming that GC matters, which is the "explicit variant"? – mushishi May 06 '19 at 16:58
  • @mushishi I meant code like `foo x y z | z==x = bar z` which one can still use if more control on GC is required. – chi May 06 '19 at 18:08
  • 4
    Allowing non-linear patterns was discussed during the design of Haskell, but we decided against it. The implicit equality comparison can hide an arbitrary amount of computation, so it felt better not to allow it. – augustss May 07 '19 at 01:20
8

Some languages with pattern matching (e.g. Prolog, Erlang) allow for

isWaiting x (Push x y p) = True 

to mean that the pattern only matches when the two pattern variables x are equivalent.

Haskell doesn't. You can read up on Pattern Matching - Prolog vs. Haskell if you like.


An alternative to your working variant that uses guards looks like:

isWaiting :: Eq a => a -> PriorityQueue a -> Bool
isWaiting x EmptyQueue = False
isWaiting x (Push z y p)
  | x == z = True
  | otherwise = isWaiting x p

And one that uses the (||) operator instead of if-then-else looks like:

isWaiting :: Eq a => a -> PriorityQueue a -> Bool
isWaiting x EmptyQueue = False
isWaiting x (Push z y p) = x == z || isWaiting x p

Edit: And one that uses Daniel Wagner's proposal of deriving Foldable:

{-# LANGUAGE DeriveFoldable #-}

type Priority = ...

data PriorityQueue a
  = EmptyQueue
  | Push a Priority (PriorityQueue a)
  deriving (Foldable)

isWaiting :: Eq a => a -> PriorityQueue a -> Bool
isWaiting = elem
sshine
  • 15,635
  • 1
  • 41
  • 66
  • Nice, I'm mad at myself for not recognizing `elem`! ...and at this point I'd even be tempted not to define `isWaiting` at all, hah. – Daniel Wagner May 06 '19 at 15:00