Reputation: 127
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
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
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