Shillington
Shillington

Reputation: 127

Haskell: Filter applied to too many arguments

I'm trying to get the index of a value in a list, doing so like this:

items = "Test"
zipItems xs = zip xs [0..]
thisItemNumber item = snd . head . filter (\(i, _) -> i == item) (zipItems items)

When I run this I get the following error:

* Couldn't match expected type `a -> [(a0, c)]'
              with actual type `[(Char, Integer)]'
* Possible cause: `filter' is applied to too many arguments
  In the second argument of `(.)', namely
    `filter (\ (i, _) -> i == item) (zipItems items)'
  In the second argument of `(.)', namely
    `head . filter (\ (i, _) -> i == item) (zipItems items)'
  In the expression:
    snd . head . filter (\ (i, _) -> i == item) (zipItems items)
* Relevant bindings include
    thisItemNumber :: Char -> a -> c (bound at <interactive>:89:5)

Which I don't understand. In my mind:

zipItems items

has type [(Char, Int)]

\(i, _) -> i == item

has type (Char, a) -> Bool

and then I'm just applying filter to a type a -> Bool and an a. How is this going wrong?

Upvotes: 2

Views: 1054

Answers (2)

willeM_ Van Onsem
willeM_ Van Onsem

Reputation: 476574

You somehow wrote a function that looks like a (wrong) mix between the a "function pipeline" (with the (.) operator), and an approach where you specify the parameter.

Since you define a chain of functions, you need to put these between brackets if you perform function application with zipItem items here, since otherwise the function application only binds with the last item of the chain: the filter (\(i, _) -> i == item). If you write:

f . g x

then this is interpeted as:

f . (g x)

But the (.) :: (b -> c) -> (a -> b) -> a -> c operator expects that the second operand is a function, where filter (\(i, _) -> i == item) (zipItems items) is a [(Char, Int)], so the two do not match.

So by adding brackets, we obtain:

thisItemNumber :: (Enum c, Num c) => Char -> c
thisItemNumber item = (snd . head . filter (\(i, _) -> i == item)) (zipItems items)

Since it is possible that the element can not be found, it might however be useful to use listToMaybe :: [a] -> Maybe a and return a Maybe c, such that it is clear that this function can fail to find the element:

import Data.Maybe(listToMaybe)

thisItemNumber :: (Enum c, Num c) => Char -> Maybe c
thisItemNumber item = (listToMaybe . map snd . filter ((item ==) . fst)) (zipItems items)

For example:

Prelude Data.Maybe> thisItemNumber 'L'
Nothing
Prelude Data.Maybe> thisItemNumber 'a'
Nothing
Prelude Data.Maybe> thisItemNumber 'T'
Just 0
Prelude Data.Maybe> thisItemNumber 'e'
Just 1
Prelude Data.Maybe> thisItemNumber 'X'
Nothing

That being said, what you here aim to do, already exists (well not with this specific values of course), with the elemIndex :: Eq a => a -> [a] -> Maybe Int function.

So the last function is equivalent to:

import Data.List(elemIndex)

thisItemNumber :: (Enum c, Num c) => Char -> Maybe c
thisItemNumber item = elemIndex item items

or we can unwrap the value from the Just data constructor with fromJust :: Maybe a -> a:

import Data.List(elemIndex)
import Data.Maybe(fromJust)

thisItemNumber :: (Enum c, Num c) => Char -> c
thisItemNumber item = fromJust (elemIndex item items)

although, as said before, this means that your function can error, which is typically not advisable.

Upvotes: 1

melpomene
melpomene

Reputation: 85767

You're using . there. The . operator creates a pipeline of functions:

(f . g) x = f (g x)

In your case the code is

snd . head . filter (\(i, _) -> i == item) (zipItems items)

snd is a function.

head is a function.

But filter (\(i, _) -> i == item) (zipItems items) is not a function, it's a list.

That's why you get an error: To be able to use ., you must give it a function.

* Couldn't match expected type `a -> [(a0, c)]'
              with actual type `[(Char, Integer)]'

says . expects some kind of function (which must return a list of tuples to satisfy snd . head), but what you're actually giving it is a list.

Possible solutions:

  • Don't use . at all:

    snd (head (filter (\(i, _) -> i == item) (zipItems items)))
    

    All of your functions are fully applied so you don't really need a function pipeline.

  • Use $ instead of parens:

    snd $ head $ filter (\(i, _) -> i == item) $ zipItems items
    

    Deeply nested code can be hard to decipher. We can get rid of ))) by using $ instead.

  • Build a function pipeline, but immediately apply it to an argument:

    (snd . head . filter (\(i, _) -> i == item) . zipItems) items
    

    Now all operands of . are functions, but we take the whole thing and apply it to items at the end.

Or you could just use the standard library and do

import Data.List

thisItemNumber item = elemIndex item items

This slightly changes the return type of thisItemNumber to Maybe Int because it's possible that item doesn't appear in items. If you want to ignore this error:

import Data.List
import Data.Maybe

thisItemNumber item = fromJust (elemIndex item items)

Upvotes: 7

Related Questions