2

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?

1 Answer 1

6

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.

Sign up to request clarification or add additional context in comments.

1 Comment

unique = (== 1) .: length .: filter is also possible. On other hand (== 1) . length .: filter :: (Num ([a] -> Int), Eq ([a] -> Int)) => (a -> Bool) -> Bool may typecheck but isn't useful.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.