Peter
Peter

Reputation: 1471

Returning a null value in Haskell without constricting Polymorphism

I'm following "Real World Haskell", and am going through chapter 2, where the exercise is to create a "lastButOne" function which returns the second to last value in the list. My original code was:

lastButOne xs =     if null (tail (tail xs))
                    then head xs
                    else lastButOne (tail xs)

Which works fine unless I give it a list with only 1 or 2 items. So I modified it to this:

lastButOne xs = if null xs || null (tail xs)
                then head xs
                else
                        if null (tail (tail xs))
                        then head xs
                        else lastButOne (tail xs)

Which checks if it only has 1 or 2 items, but then fails if it only has 1 item because of the call to head. My problem is that I can't think of anything else to return other than "head xs", ideally I want it to return null or something like that, but I can't find a "null" that allows the function to still be polymorphic.

Upvotes: 12

Views: 13827

Answers (5)

Matvey Aksenov
Matvey Aksenov

Reputation: 3900

Perhaps you are looking for a Maybe type:

lastButOne :: [a] -> Maybe a
lastButOne [x,_] = Just x
lastButOne (_:y:ys) = lastButOne (y:ys)
lastButOne _ = Nothing

Upvotes: 28

John F. Miller
John F. Miller

Reputation: 27227

[I]deally I want it to return null or something like that, but I can't find a "null" that allows the function to still be polymorphic.

There are a number of answers already in posted on how to make this work, so instead let me answer the more fundamental question of "Why isn't there a 'null'?"

This is part of the philosophy of the type-checker in Haskell. most languages include some form of a special value that signals an error or lack of data. Examples include 0,-1 "" (the empty string) JavaScripts null or Ruby's nil. The problem is that this creates a whole class of potential bugs wherein the calling function only deals with "good" values and the program crashes or worse corrupts data when a special value is returned. There is also the issue of the special value being intended as a regular value -- that is 0 actually being a number and not a failure state.

In your problem, the question could be framed, "Would the function that calls lastButOne know what to do with null?"

The problem of not having a valid answer for certain otherwise well-formed inputs is a very common one in Haskell and has two typical solutions. The first is taken from Erlang: Fail quickly and cleanly. Under this solution it is the responsibility of the caller to ensure that the data is "good." If it is not the program dies with an error (or the exception is caught, but this is more difficult in Haskell then other languages) This is the solution used by most list functions e.g. head, tail, etc...

The other solution is to explicitly state that the function may fail to produce good data. The Maybe a type is the simplest of these solutions. It allows you to return a value Nothing that works much like the null you are looking for. But there is a cost to this. Your function's type signature becomes lastButOne :: [a] -> Maybe a. Now every calling function must take not an a but a Maybe a, and before it can get its answer, it must tell Haskell what it is going to do if the answer is Nothing.

Upvotes: 10

nponeccop
nponeccop

Reputation: 13677

An example of "extended error handling" mentioned by @Andrew Jaffe could be:

{-# LANGUAGE FlexibleContexts #-} module Main where

import Control.Monad.Error

lastButOne :: MonadError String m => [a] -> m a
lastButOne (_ : y : []) = return y 
lastButOne (_ : t) = lastButOne
lastButOne _ = throwError "Error in lastButOne"

In this case, it is possible to return Either type: Right x in case of success and Left "Error in lastButOne" in case of failure, but many other instances of MonadError String m are possible.

Also read 8 ways to report errors in Haskell for more possibilities.

Upvotes: 0

Victor
Victor

Reputation: 385

Why don't you use pattern matching to handle the case where you have only 1 or 2 elements? Something like:

lastButOne :: [a] -> Maybe a
lastButOne [x,y] = Just x
lastButOne (x:y:t) = lastButOne (y:t)
lastButOne  _ = Nothing

Upvotes: 3

Andrew Jaffe
Andrew Jaffe

Reputation: 27097

Well, this depends on how much error-checking you actually want to do.

I suspect that, given this is only in Chapter 2, you are probably meant to ignore this edge case. Haskell itself throws an exception:

Prelude> head []
*** Exception: Prelude.head: empty list

The alternative, if this is a case that you will have to deal with a lot, would be to change the return type of lastButOne to Maybe a and return Just (head xs) when it works, and Nothing when it fails. But this means that you would always have deal with living inside the Maybe monad, which may not be ideal (as you would have to "unwrap" the Just value to use it).

Upvotes: 5

Related Questions