Home > Enterprise >  Why does this '@' usage in Haskell fail to encompass the whole list?
Why does this '@' usage in Haskell fail to encompass the whole list?

Time:09-17

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
  • Related