Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell Declare empty list, but which is actually not empty?

I'm new to Haskell and are playing around a bit. I created a recursive function with guards. See function below:

filterAge :: [Person] -> [String]
filterAge (x:xs)
 | (x:xs) == []                        = []
 | (getAge x) < 30 || (getAge x) > 40  = [] ++ filterAge xs
 | otherwise                           = [getName x] ++ filterAge xs

I have a data set created with 10 Persons which I use in this method. When I tried this function it gave all the right people, but after that it got a Non-exhaustive patterns error: ["Lise","Jaap","Elle","Ebba"*** Exception: D:\...:(44,1)-(47,77): Non-exhaustive patterns in function filterAge

I found out that it never reaches the first guard. So I played around a bit and found out something really strange (in my opinion):

*Main> let (x:xs) = []
*Main> (x:xs) == []
False

Now my main question is: Why does (x:xs) == [] return False?

If anyone has a better way for me of doing the function that'd be great, but it's not very important.

Thanks in advance!

EDIT

Thanks to Willem Van Onsem and Lambda.xy.x I got a quick answer to my question. This resulted in the following function which works perfectly:

filterAge :: [Person] -> [String]
filterAge []                           = []
filterAge (x:xs)
 | (getAge x) < 30 || (getAge x) > 40  = [] ++ filterAge xs
 | otherwise                           = [getName x] ++ filterAge xs

But for the best version you'd have to check the answer of Willem Van Onsem.

like image 655
Snake Avatar asked Dec 07 '22 17:12

Snake


1 Answers

A list is defined as:

data [] a = [] | a : [a]

So there are two constructors for a list: [] the empty list, and (x:xs) a constructor with one element, and a tail that can store an arbitrary number (zero or more) remaining elements.

Therefore (x:xs) is a list with at least one element: the x. The xs can be an empty list (since it has type [a]), but x has type a so that is the "head" of the list. Your let statement works with pattern matching, and since the empty list cannot match with (x:xs), it will always fail.

Another implication is that your first guard can never fire. In order to fix the issue, you should implement a separate case for the empty list. Like:

filterAge :: [Person] -> [String]
filterAge []     = [] -- empty list case
filterAge (x:xs) -- first guard dropped
    | (getAge x) < 30 || (getAge x) > 40  = [] ++ filterAge xs
    | otherwise                           = [getName x] ++ filterAge xs

Note that we dropped the first guard in the second clause, since we know that will always fail, and thus checking this will (potentially) only cost CPU cycles.

There are still some parts we can optimize:

  1. we call getAge twice, which is useless, we can use a where clause to optimize this;
  2. [] ++ somelist is simply somelist: appending after an empty list results in that list; and
  3. [element] ++ somelist is element : somelist, since now we work with the list constructor directly.

So our filterAge can be rewritten into:

filterAge :: [Person] -> [String]
filterAge []     = [] -- empty list case
filterAge (x:xs) | age < 30 || age > 40  = filterAge xs
                 | otherwise             = getName x : filterAge xs
    where age = getAge x

Note that if you compile (or start the interpreter) with the -Wincomplete-patterns flag (warnings for incomplete patterns), Haskell will automatically warn you that your function definitions are not complete, and that there are input patterns for which you have not defined a clause.

like image 193
Willem Van Onsem Avatar answered Dec 14 '22 23:12

Willem Van Onsem