Why use null function instead of == [] to check for empty list in Haskell?
ListHaskellIs EmptyEmpty ListList Problem Overview
I am reading through the "Starting Out" chapter of Learn You a Haskell for Great Good!. It says:
> null
checks if a list is empty. If it is, it returns True
, otherwise it returns False
. Use this function instead of xs == []
(if you have a list called xs
)
I tried in ghci:
xs = [] -- and then,
xs == []
null xs
Both of them are True
.
I wonder what's the difference.
Should I use the null
function instead of == []
and why?
List Solutions
Solution 1 - List
You should use null
. In most cases it doesn't matter, but it is a good habit to get into anyway, because occasionally you may want to check if a list of non-comparable things is empty. Here is a short, crisp example showing this difference:
> null [id]
False
> [id] == []
<interactive>:1:1: error:
• No instance for (Eq (a0 -> a0)) arising from a use of ‘==’
(maybe you haven't applied a function to enough arguments?)
• In the expression: [id] == []
In an equation for ‘it’: it = [id] == []
Solution 2 - List
There is a difference. In order to use x == []
, the type of the elements of the list should be a member of the Eq
typeclass. Indeed, checking the equality of two lists is defined by the instance declaration:
instance Eq a => Eq [a] where
[] == [] = True
(x:xs) == (y:ys) = x == y && xs == ys
_ == _ = False
That means that you can not use x == []
if x
is for example a list of IO Int
s.
null :: [a] -> Bool
on the other hand, uses pattern matching. This is implemented as:
null :: [a] -> Bool
null [] = True
null (:) = False
So regardless what type the elements of the list are, it will always typecheck.
Solution 3 - List
In addition to the good answers given so far, null
actually has type
null :: Foldable t => t a -> Bool
I don't know if you've gotten to typeclasses in LYAH, but the short of it is that null
can be used not just for lists, but for any data structure that implements null
.
This is to say that using null
on a Map
or a Set
is valid, too.
> null Map.empty
True
> null (Map.singleton 1)
False
> null Set.empty
True
> null (Set.singleton 1)
False
> null []
True
> null [1]
False
I don't think it's especially common to write functions that need to be this general, but it doesn't hurt to default to writing more general code.
A side note
In many cases, you'll end up wanting to use a function like null
to do conditional behavior on a list (or other data structure). If you already know that your input is a specific data structure, it's more elegant to just pattern match on its empty case.
Compare
myMap :: (a -> b) -> [a] -> [b]
myMap f xs
| null xs = []
myMap f (x:xs) = f x : myMap f xs
to
myMap' :: (a -> b) -> [a] -> [b]
myMap' f [] = []
myMap' f (x:xs) = f x : myMap' f xs
In general, you should try to prefer pattern matching if it makes sense.
Solution 4 - List
Also a simple function that filter all empty list would fail:
withoutEmpty = filter (== [])
and that would be:
withoutEmpty = filter null
notice that:
withoutEmpty ls = filter (==[]) ls
will work just fine, but the important point is that in some cases like the other one could fail.
Also look at @cole answer, it complements all the answers here, the typeclass Foldable has the null
function there to be implemented:
To see more info of Foldable here