TorbenJ
TorbenJ

Reputation: 4582

Implementation of null function

I have to learn Haskell for university and therefor I'm using learnyouahaskell.com for the beginning.
I always used imperative languages so I decided to practice Haskell by coding a lot more than I would for other languages.
I started to implement several functions to work with lists such as head, tail, init,...
At some point I looked up the implementations of these functions to compare to mine and I stumbled upon the null function defined in List.lhs.

null's implementation:

-- | Test whether a list is empty.
null                    :: [a] -> Bool
null []                 =  True
null (_:_)              =  False

my implementation:

mNull :: [a] -> Bool
mNull []        = True
mNull _         = False

I know there are no stupid questions even for such simple questions :)
So my question is why the original implementation uses (_:_) instead of just _?
Is there any advantage in using (_:_) or are there any edge cases I don't know of?
I can't really imagine any advantage because _ catches everything.

Upvotes: 26

Views: 1972

Answers (3)

leftaroundabout
leftaroundabout

Reputation: 120731

You can't just turn the clauses around with your solution:

mNull' :: [a] -> Bool
mNull' _  = False
mNull' [] = True

this will always yield False, even if you pass an empty list. Because the runtime doesn't ever consider the [] clause, it immediately sees _ matches anything. (GHC will warn you about such an overlapping pattern.)

On the other hand,

null' :: [a] -> Bool
null' (_:_) = False
null' []    = True

still works correctly, because (_:_) fails to match the particular case of an empty list.

That in itself doesn't really give the two explicit clauses an advantage. However, in more complicated code, writing out all the mutually excluse options has one benefit: if you've forgotten one option, the compiler can also warn you about that! Whereas a _ can and will just handle any case not dealt with by the previous clauses, even if that's not actually correct.

Upvotes: 42

dfeuer
dfeuer

Reputation: 48611

I'll add on to what leftaroundabout said. This is not really a potential concern for the list type, but in general, data types sometimes get modified. If you have an ill-conceived

data FavoriteFood = Pizza
                  | SesameSpinachPancake
                  | ChanaMasala

and later you learn to like DrunkenNoodles and add it to the list, all your functions that pattern match on the type will need to be expanded. If you've used any _ patterns, you will need to find them manually; the compiler can't help you. If you've matched each thing explicitly, you can turn on -fwarn-incomplete-patterns and GHC will tell you about every spot you've missed.

Upvotes: 5

shree.pat18
shree.pat18

Reputation: 21757

Because _ literally means anything apart from explicitly specified patterns. When you specify (_:_) it means anything which can be represented as a list containing at least 1 element, without bothering with what or even how many elements the list actually contains. Since the case with an explicit pattern for empty list is already present, (_:_) might as well be replaced by _.

However, representing it as (_:_) gives you the flexibility to not even explicitly pass the empty list pattern. In fact, this will work:

-- | Test whether a list is empty.
null                    :: [a] -> Bool    
null (_:_)              =  False
null _                  =  True

Demo

Upvotes: 6

Related Questions