Partial functions are functions that are not defined for all possible arguments of its specified type.
The most common example of a partial function is head
. It has the innocent-looking type signature of
head :: [a] -> a
head
fails when an empty list is given:
head [1,2,3] -- 1
head [] -- Exception!
Because of Haskell’s type erasure, head
doesn’t even know what the supplied type is, so there’s no way for it to return a value of type a
when there are no values in the list (besides the obvious fact that there are no values in the list).
In Brent Yorgey’s CIS 194 Haskell course:
head is a mistake! It should not be in the Prelude. Other partial Prelude functions you should almost never use include tail, init, last, and (!!).
Haskell’s official wiki provides a complete list of partial functions in Prelude. Note that some of these functions are considered partial functions because they do not terminate if given an infinite list.
Given all that, I think using something like head
is okay if composed with other functions that guarantee non-emptiness of the list, or if the function type signature is NonEmpty
, a type class which guarantees a list with at least one element.
For example, consider lastItem
, a function which returns the last item in a list:
lastItem :: [a] -> a
lastItem = head . reverse
lastItem [1,2] -- 2
lastItem [] -- Exception!
In addition, consider toDigits
, a function which, when given an Integral
value, returns a list of its constituent digits:
toDigits :: Integral a => a -> [a]
toDigits x
| x < 0 = toDigits $ x * (-1)
| x < 10 = [x]
| otherwise = toDigits (div x 10) ++ [mod x 10]
toDigits 123 -- [1,2,3]
toDigits 0 -- [0]
toDigits (-43) -- [4,3]
A function like toDigits
guarantees a non-empty list, and when combined with lastItem
, we can get the last digit of an Integral
value:
lastDigit :: Integral a => a -> Int
lastDigit = lastItem . digits
lastDigit 123 -- 3
lastDigit 01 -- 1
lastDigit (-1) -- 1
Or consider (!!)
, which accesses an element in a list by index. If the index provided is too large, an exception is thrown:
(!!) [1,2,3] 0 -- 1
(!!) [1,2,3] 4 -- Exception!
An idea is to wrap (!!)
with the Maybe
data type in a function like this:
findByIndex :: [a] -> Int -> Maybe a
findByIndex xs index
| index >= length xs = Nothing
| otherwise = Just ((!!) xs index)
findByIndex [1,2,3] 0 -- Just 1
findByIndex [1,2,3] 4 -- Nothing
I’m surprised that such commonly used functions in the Prelude are so dangerous, so it’s good to pay attention when using them. Partial functions like head
are easy to replace with pattern matching, but others may be harder to supplant.