2
votes

I'm a Haskell beginner working through an exercise in Chapter 2 of Real World Haskell, where you write a function that returns the second-to-last element of a list.

I've written the following function to attempt to solve this:

-- file: \Learn Haskell\lastButOne.hs
lastButOne xs = if length xs == 2
                then head xs
                else lastButOne tail xs

What I don't understand is this error the compiler throws:

lastButOne.hs:2:1: error:
    • Couldn't match type ‘[a] -> [a]’ with ‘[t]’
      Expected type: ([a] -> [a]) -> [t] -> t
        Actual type: [t] -> t
    • Relevant bindings include
        lastButOne :: ([a] -> [a]) -> [t] -> t (bound at lastButOne.hs:2:1)

From what I understand, ghci thinks my function ought to have a different type than it does, but I don't understand why that's happening, or how I can fix it.

Edit: Thanks for the answers! I've updated my code to:

-- file: \Learn Haskell\lastButOne.hs
lastButOne xs = if length xs == 2
                then head xs
                else if length xs < 2
                     then error "This list does not have a second-to-last element."
                     else lastButOne (tail xs)

This eliminates the error where tail xs is being interpreted as two arguments, rather than a single expression. I also added some code that ensures the list isn't too short. Willem Van Onsem's solution is nicer, but as an exercise I thought I'd come up with a solution that only used the concepts introduced so far in the book.

1
lastButOne tail xs is (lastButOne tail) xs, not lastButOne (tail xs) - amalloy
You should avoid head, tail as much as possible. They are not idiomatic Haskell, since 1) they can crash your program if you ever use them on the empty list, and 2) pattern matching is easier to use and more convenient, too. - chi

1 Answers

3
votes

The problem is with the line:

        else lastButOne tail xs

It should be:

        else lastButOne (tail xs)

Otherwise ghci things that you give lastButOne two arguments: tail and xs.

You can however make the code more elegant:

lastButOne [x,_] = x
lastButOne xs = lastButOne $ tail xs

Furthermore you will have to find a way to resolve lists with one element, and the empty list. Right now the function will start looping. An idea could be to to error on these lists, like:

lastButOne [x,_] = x
lastButOne (_:xs) = lastButOne xs
lastButOne [] = error "The list has no last but one element."