AdamK
AdamK

Reputation: 409

Function which returns a new list with the first and last elements removed

I am very new to Haskell and am trying a simple exercise for practice. I want to write a function that removes the first and last elements of a list.

I do not need to check if the length of the list. I found in a book I have a way to remove the first element and a way to reverse the list, but I think this may be more complex than it has to be. I also know that complexity aside, what I have is wrong after the second line, but I don't know why or how to fix it.

Here is my code:

midList :: [a] -> [a]
midList [a] = tail [a]
[a] = reverse [a]
[a] = tail [a]
[a] = reverse [a]

Upvotes: 1

Views: 2260

Answers (2)

chi
chi

Reputation: 116139

You need to compose your steps functionally. Your proposed code could be rewritten as:

midList :: [a] -> [a]
midList xs = reverse (tail (reverse (tail xs)))

Note that tail will crash your program if it is ever passed the empty list. You can, and should, avoid that by either 1) checking if the list has at least two elements, or 2) using drop 1 instead of tail, which will simply return [] on the empty list.

If you try 2), you can keep your function type. For approach 1), instead, you can report the error to the caller using a Maybe [a] return type. More concretely:

midList :: [a] -> Maybe [a]
midList xs@(_:_:_) = Just (reverse (tail (reverse (tail xs))))
midList _          = Nothing

where the pattern _:_:_ stands for a list which has at least two elements.

Alternatively, use both init and tail: the former removes the last element, the latter removes the first element.

midList :: [a] -> Maybe [a]
midList xs@(_:_:_) = Just (init (tail xs))
midList _          = Nothing

Be however quite careful here, since in general it is best to avoid partial functions like init and tail. As a general rule, one should never use them if a more basic approach does the job, e.g. using pattern matching.

Here, I would find the code above to be suboptimal. I'd prefer the following, coding my own "safe" version of init to remove the last element, if any, or report the error otherwise.

midList :: [a] -> Maybe [a]
midList (_:xs@(_:_)) = safeInit xs
midList _            = Nothing

safeInit :: [a] -> Maybe [a]
safeInit [] = Nothing
safeInit (x:xs) = case safeInit xs of
   Nothing -> Just []
   Just ys -> Just (x:ys)

Upvotes: 1

dfeuer
dfeuer

Reputation: 48581

The first thing to do is to make sure you understand the problem. In particular, what do you want to happen if the list has no elements, or just one? The easiest thing is probably to just return the empty list in that case. So let's do that. We can break up the problem into two pieces:

  1. Remove the first element of a list
  2. Remove the last element of a list

The Haskell prelude defines a couple functions we really don't want, so let's hide them:

import Prelude hiding (tail, init)

Now we can define better versions of those functions. tail will return all but the first element of a list and init will return all but the last element. Then we can write

chop :: [a] -> [a]
chop xs = init (tail xs)

tail is the easiest to write. I'll provide the skeleton and you can fill in the missing bits.

tail :: [a] -> [a]
tail [] = ????
tail (x : xs) = ????

init is only a little bit trickier. Note that one of the cases will be recursive.

init :: [a] -> [a]
init [] = ????
init [x] = ????
init (x : xs) = ????

Upvotes: 4

Related Questions