This is working
unique :: (a -> Bool) -> [a] -> Bool
unique p xs = 1 == length (filter p xs)
But now I want it in the form:
unique = (== 1) . length . filter
Error message:
Couldn't match expected type `[a] -> Bool' with actual type `Bool'
Expected type: b0 -> [a] -> Bool
Actual type: b0 -> Bool
In the first argument of `(.)', namely `(== 1)'
In the expression: (== 1) . length . filter
Why is this not working?
This is because filter
is a two argument function. You can get around this using the handy operator
(.:) = (c -> d) -> (a -> b -> c) -> a -> b -> d
(.:) = (.) . (.)
-- Important to make it the same precedence as (.)
infixr 9 .:
unique = ((== 1) . length) .: filter
If you look at the type of (length .)
in GHCi, you'll get
(length .) :: (a -> [b]) -> a -> Int
This means that it takes a single argument function that returns a list. If we look at the type of filter
:
filter :: (a -> Bool) -> [a] -> [a]
This can be rewritten to make it "single argument" as
filter :: (a -> Bool) -> ([a] -> [a])
And this quite clearly does not line up with a -> [b]
! In particular, the compiler can't figure out how to make ([a] -> [a])
be the same as [b]
, since one is a function on lists, and the other is simply a list. So this is the source of the type error.
Interestingly, the .:
operator can be generalized to work on functors instead:
(.:) :: (Functor f, Functor g) => (a -> b) -> f (g a) -> f (g b)
(.:) = fmap fmap fmap
-- Since the first `fmap` is for the (->) r functor, you can also write this
-- as (.:) = fmap `fmap` fmap === fmap . fmap
What is this good for? Say you have a Maybe [[Int]]
, and you wanted the sum of each sublist inside the Just
, provided it exists:
> let myData = Just [[3, 2, 1], [4], [5, 6]]
> sum .: myData
Just [6, 4, 11]
> length .: myData
Just [3, 1, 2]
> sort .: myData
Just [[1,2,3],[4],[5,6]]
Or what if you had a [Maybe Int]
, and you wanted to increment each one:
> let myData = [Just 1, Nothing, Just 3]
> (+1) .: myData
[Just 2,Nothing,Just 4]
The possibilities go on and on. Basically, it lets you map a function inside two nested functors, and this sort of structure crops up pretty often. If you've ever had a list inside a Maybe
, or tuples inside a list, or IO
returning a string, or anything like that, you've come across a situation where you could use (.:) = fmap fmap fmap
.