Reputation: 1035
I have started to solve the 99 problems in Haskell, and for the second question, it is given the following solution:
myButLast' :: [a] -> a
myButLast' = last . init
and if we give the empty list to this function, we get and error, however, I would like to print a specific error as
myButLast' [] = error "The list has to have at least 2 elements!"
myButLast' [x] = error "The list has to have at least 2 elements!"
but when I add these line to the code, I get
Equations for ‘myButLast'’ have different numbers of arguments
, so is there a way to use the composition type of defining my new function while also adding some specific behaviour ?
Upvotes: 1
Views: 127
Reputation: 714
If you would use wrappers you can have the best of both worlds and a clear separation between the two. Robust error checking and reporting and a vanilla function to use however you wish, with or without the wrapper. Interesting, the vanilla function reports 'last' cannot process an empty list given a one element list and 'init' cannot process an empty list when given an empty list.
mbl2 = last . init
mbl xs = if length xs < 2 then error errmsg else mbl2 xs
where errmsg = "Input list must contain at least two members."
Upvotes: 1
Reputation: 71400
You could often simply compose an additional function that has the additional behaviour:
myButLast' :: [a] -> a
myButLast' = last . init . assertAtLeastTwo
where assertAtLeastTwo xs@(_:_:_) = xs
assertAtLeastTwo _ = error "The list has to have at least 2 elements!"
Here we've added a function that checks for the conditions we want to raise an error, and otherwise simply returns its input so that the other functions can act on it exactly as if assertAtLeastTwo
wasn't there.
Another alternative that allows you to clearly highlight the error conditions is:
myButLast' :: [a] -> a
myButLast' [] = error "The list has to have at least 2 elements!"
myButLast' [x] = error "The list has to have at least 2 elements!"
myButLast' xs = go xs
where go = last . init
Where you do the error checking as you originally wrote, but have the main definition simply defer to an implementation function go
, which can then be defined point-free using composition.
Or you can of course inline go
from above, and have:
myButLast' xs = (last . init) xs
Sine a composition of functions is itself an expression, and can simply be used in a larger expression directly as the function. In fact a fairly common style is in fact to write code of the form "compose a bunch of functions then apply to this argument" this way, using the $
operator:
myButLast' xs = last . init $ xs
Upvotes: 1
Reputation: 530843
Somewhat sidestepping the original question, but you may be interested in the safe
package for tasks like this. In general, you should strive to use total functions that don't raise errors. In this case, that means using something like lastMay :: [a] -> Maybe a
and initMay :: [a] -> Maybe a
, which simply return Nothing
if given an empty list. They can be composed using <=<
, found in Control.Monad
.
import Safe
myButLast :: [a] -> Maybe a
myButLast = lastMay <=< initMay
Then
> myButLast []
Nothing
> myButLast [1]
Nothing
> myButLast [1,2]
Just 1
If you really want an error message, Safe
provides lastNote
and initNote
as well.
myButLast = let msg = "Need at least 2 elements" in (lastNote msg . initNote msg)
Upvotes: 1
Reputation: 50819
The best you can do is probably something like the following, where the error checking is moved into an auxiliary function (which I've named go
for lack of a better name) defined in a where
clause:
myButLast :: [a] -> a
myButLast = go (last . init)
where
go _ [] = bad
go _ [x] = bad
go f xs = f xs
bad = error "The list has to have at least 2 elements!"
It might help make it clearer what's going on if we define go
separately with a type signature. Also, in case you find the underscores confusing, I've replaced them with f
:
myButLast :: [a] -> a
myButLast = go (last . init)
go :: ([a] -> a) -> [a] -> a
go f [] = bad
go f [x] = bad
go f xs = f xs
bad = error "The list has to have at least 2 elements!"
Here, you can see that go
is a function that takes two arguments, the first being itself a function of type [a] -> a
and the second being a list of type [a]
.
The above definition of go
pattern matches on the second argument (the list). If the list is empty or a singleton, then the result of go
is just bad
(the error message), regardless of the function f
. Otherwise (if the list is at least two elements), the result of go f xs
is simply to apply the first argument (that function f
) to the list xs
.
How does this work? Well, let's see what happens if we apply myButLast
to a list. I've used the symbol "≡" here to show equivalence of Haskell expressions with comments explaining why they are equivalent:
myButLast [1,2,3]
-- by the definition of "myButLast"
≡ go (last . init) [1,2,3]
-- by the definition of "go", third pattern w/
-- f ≡ last . init
-- xs = [1,2,3]
≡ (last . init) [1,2,3] -- this is just f xs w/ the pattern substitutions
-- because of your original, correct answer
≡ 2
If we apply it to a "bad" list, the only difference is the pattern matched from the definition of go
:
myButLast [1]
-- by the definition of "myButLast"
≡ go (last . init) [1]
-- by the definition of "go", second pattern w/
-- f ≡ last . init
-- x = 1
≡ bad
-- gives an error message by definition of "bad"
As an interesting aside, another way to look at go
is that it's a function:
go :: ([a] -> a) -> ([a] -> a)
Because the function application arrow ->
is right associative, this type signature is exactly the same as ([a] -> a) -> [a] -> a
. The neat thing about this is that now it's clear that go
takes a function of type [a] -> a
(such as last . init
) and returns another function of type [a] -> a
(such as myButLast
). That is, go
is a transformer that adds additional behavior to an existing function to create a new function, which is exactly what you were asking for in your original question.
In fact, if you slightly generalize the type signature so that go
can operate on a function taking a list, regardless of what it returns:
go :: ([a] -> b) -> [a] -> b
go _ [] = bad
go _ [x] = bad
go f xs = f xs
this still works. Then, you could use this same go
on anything that needed a list of length two, whatever it returned. For example, if you had an original implementation to return the last two elements of a list:
lastTwoElements :: [a] -> [a]
lastTwoElements = (!! 2) . reverse . tails -- tails from Data.List
you could re-write it as:
lastTwoElements :: [a] -> [a]
lastTwoElements = go ((!! 2) . reverse . tails)
to add error handling for the empty and singleton list cases.
In this case, you'd probably want to rename go
to usingTwoElements
or withList2
or something...
Upvotes: 3
Reputation: 116139
Use an explicit argument in the solution:
myButLast' x = last (init x)
Now you can add your special cases just above that line.
The original solution used a pointfree style last . init
to avoid mentioning the x
argument. However, if you have to add further equations, you need to make the argument explicit.
Moving from
fun :: A -> B
fun = something
to
fun :: A -> B
fun a = something a
is called eta-expansion, and is a common transformation of Haskell code. The first style is usually called point-free (or, jokingly, point-less), while the second one is called pointful. Here "point" refers to the variable a
.
Upvotes: 2