Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can't ++ be used in pattern matching?

From LearnYouAHaskell:

One more thing — you can't use ++ in pattern matches. If you tried to pattern match against (xs ++ ys), what would be in the first and what would be in the second list? It doesn't make much sense. It would make sense to match stuff against (xs ++ [x,y,z]) or just (xs ++ [x]), but because of the nature of lists, you can't do that.

I'm struggling to reason about what he means by the nature of lists why this can't be.

like image 533
user2415706 Avatar asked Apr 21 '14 20:04

user2415706


2 Answers

You pattern match against constructors, and for lists there are two constructors, the empty list [] and 'cons' :, where cons has two arguments, the head of the list, and the tail. ++ is a function to append two lists, so you can't match against it.

If you could match against it, there would be multiple possible matches for xs and ys in the pattern xs ++ ys. For example if you took even a small list like [1] then there are two possiblities

xs == [] and ys == [1]
xs == [1] and ys == []

so the match is ambiguous which is what the quote is about.

like image 136
Lee Avatar answered Oct 15 '22 16:10

Lee


You can only pattern match on data constructors. This is made confusing because the List type in Haskell relies on some syntatic sugar. So let's remove that and define our own List type so it's easier to see what's going on.

data List a = Nil | Cons a (List a)

Now when you want to declare a function that uses this List type, you can pattern match on the constructors.

myHead :: List a -> Maybe a
myHead Nil = Nothing
myHead (Cons a as) = Just a

This is the only kind of pattern matching you can do by default. List in Haskell is implemented with some sugar that renames Cons to (:) and Nil to []. And it let's you write your lists in [a, b, c] notation, but fundamentally, these things are the same.

(++) on the other hand is a normal function and not a data constructor.

My guess on what you're trying to do is look at the first few elements in a list and separate them from the rest of the list. You can do this in a pretty straightforward way using normal pattern matching.

getFirstThree :: [a] -> Maybe (a, a, a)
getFirstThree (a1:a2:a3:as) = Just (a1, a2, a3)
getFirstThree _ = Nothing

If you could pattern match on normal functions, this would be the same as writing:

getFirstThree :: [a] -> Maybe (a, a, a)
getFirstThree ([a1, a2, a3] ++ as) = Just (a1, a2, a3)
getFirstThree _ = Nothing

And maybe this second definition is more clear to you. I don't believe that this is the case for lists, but there are other data types where this might be true. For this sort of thing, the ViewPatterns and PatternSynonyms extensions exist and would let you define (++) as a matchable pattern by basically telling the compiler how to perform the pattern match. However, as @Lee notes, this match is ambiguous because there are multiple valid matches for any given pattern that looks like (a ++ b).

like image 20
cassandracomar Avatar answered Oct 15 '22 15:10

cassandracomar