Our
Our

Reputation: 1035

Defining new function as a composition of functions while adding additional behaviours

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

Answers (5)

fp_mora
fp_mora

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

Ben
Ben

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

chepner
chepner

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

K. A. Buhr
K. A. Buhr

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

chi
chi

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

Related Questions