I'm working through the 99 Haskell Questions and attempted to use this piece of code to solve the second one, which requires returning the second to last element of an array:
myPen :: [a] -> a
myPen [] = error "Empty lists have no penultimate element."
myPen list@(_:xs)
| length list == 1 = error "Lists size 1 have no penultimate element."
| length xs == 2 = head xs
| otherwise = myPen xs
This code compiles, but then when given the input
myPen [1,2]
it produces the error
*** Exception: Lists size 1 have no penultimate element.
CallStack (from HasCallStack):
error, called at prob1_10.hs:16:25 in main:Main
As far as I understand @
is supposed to allow referencing the whole list in this case, and yet it seems to only be referencing the xs
or tail portion of the list.
This seems to support the thesis that it should include both x
and xs
.
My questions are two:
1.) Why is this? 2.) What is the proper way to refer to the whole list?
CodePudding user response:
What happens in this case is actually that length list == 2
, but length xs == 1
, so neither the first or second guard will match. The otherwise
guard will be taken and call the function recursively on xs
which has length 1 so the first guard will be taken and the error message will be printed. You can fix it by writing:
myPen :: [a] -> a
myPen [] = error "Empty lists have no penultimate element."
myPen list@(_:xs)
| length list == 1 = error "Lists size 1 have no penultimate element."
| length list == 2 = head list
| otherwise = myPen xs
But in Haskell it is often more common to do this with pattern matching instead of the length
function, like so:
myPen :: [a] -> a
myPen [] = error "Empty lists have no penultimate element."
myPen [_] = error "Lists size 1 have no penultimate element."
myPen [x,_] = x
myPen (_:xs) = myPen xs
CodePudding user response:
Haskell evaluates conceptually top-to-bottom. This means it first looks whether the list is empty. Then it looks at the length of list
, which is two, so that guard will not fire. Then it looks if length xs
is 2
, but that isn't the case either since xs
is the tail of the list, so [2]
which has length 1
.
Eventually it thus makes a recursive call with myPen [2]
, and then the first guard (length list
) is indeed one, and thus it raises an error.
You likely thus want to check the entire list with:
myPen :: [a] -> a
myPen [] = error "Empty lists have no penultimate element."
myPen list@(_:xs)
| length list == 1 = error "Lists size 1 have no penultimate element."
| length list == 2 = head list -- check length of list
| otherwise = myPen xs
Using length
however is not advisable, since it takes linear time to determine the length, and it gets stuck in an infinite loop for lists with an infinite length. You better work with pattern matching so:
myPen :: [a] -> a
myPen [] = error "Empty lists have no penultimate element."
myPen [_] = error "Lists size 1 have no penultimate element."
myPen [_,x] = x
myPen (x:xs) = pen xs