2

I am having some troubles with function composition and types. I would like to compose filter (which returns a list) with len, which takes a list as an argument (technically a Foldable but I am simplifying here). Looking at the types everything is as expected:

> :t length
length :: Foldable t => t a -> Int

> :t filter
filter :: (a -> Bool) -> [a] -> [a]

So now I would expect the type of (len . filter) to be

(length . filter) :: (a -> Bool) -> [a] -> Int

while in reality is

> :t (length . filter)
(length . filter) :: Foldable ((->) [a]) => (a -> Bool) -> Int

So it seems I lost some arguments. Is it included in the the Foldable requirements in some way I am not understanding?

Note that everything works as expected if I do partial application:

> let myFilter = filter odd
> :t myFilter
myFilter :: Integral a => [a] -> [a]
> :t (length . myFilter)
(length . myFilter) :: Integral a => [a] -> Int
>  (length . myFilter) [1,2,3]
2
Will Ness
  • 70,110
  • 9
  • 98
  • 181
meto
  • 3,425
  • 10
  • 37
  • 49
  • 2
    To get the type of composition that you are expecting, each function that you're composing should take 'one' argument. I put one in quotes because all functions take one argument. See more [here](https://wiki.haskell.org/Currying) – pdexter Jun 22 '16 at 16:08
  • 3
    Note that `(length . filter) x y` is `length (filter x) y` and not `length (filter x y)`. Also, `filter` is actually a function taking _one_ argument `a->Bool` and returning a function `[a]->[a]` (recall currying), which the composition is trying to pass to `length`. – chi Jun 22 '16 at 16:35
  • If something like `Num [a]` or anything with `(->)` turns up in a constraint (i.e. on the left of a `=>`), it's generally a sign that you've basically created a type error, but GHC is too polite to call it such because someone could _in principle_ define some ridiculous class instance that would make it legal. – leftaroundabout Jun 22 '16 at 22:36

3 Answers3

5

The right composition would be:

(length .) . filter :: (a -> Bool) -> [a] -> Int

which is equivalent to:

\pred xs -> length $ filter pred xs

as in:

\> let count = (length .) . filter
\> :type count
count :: (a -> Bool) -> [a] -> Int
\> count odd [1..3]
2
\> count even [1..3]
1
behzad.nouri
  • 74,723
  • 18
  • 126
  • 124
  • Just double checking I am following correctly: `(length . ) . filter p = length . (filter p)` and `length . (filter p) xs = length ( (filter p) xs)`. sorry for all the extra `()` but it's helpful for me to understand. – meto Jun 22 '16 at 18:05
  • 1
    @meto `((f .) . g) x` is `f . (g x)` and `(f . (g x)) y` is `f (g x y)` – behzad.nouri Jun 22 '16 at 18:33
4

Definitions:

(.) :: (b -> c) -> (a -> b) -> a -> c
filter ::  (m -> Bool) -> [m] -> [m]
length :: Foldable t => t n -> Int

What is u?

length . filter :: u
≡ (.) length filter :: u

Then we must solve a, b, c, t, n:

a -> b ~ (m -> Bool) -> [m] -> [m]
b -> c ~ Foldable t => t n -> Int

It follows:

a ~ m -> Bool
b ~ Foldable t => t n
b ~ [m] -> [m]
c ~ Int

Trivially:

a = m -> Bool
b = [m] -> [m]
c = Int

We have to solve t, n from b ~ Foldable t => t n, i.e. [m] -> [m] ~ Foldable t => t n.

t = ((->) [m])
n = [m]

Therefore, t n = [m] -> [m] which trivially unifies.

Summarising:

(.) :: Foldable ((->) [m]) =>
          (([m] -> [m]) -> Int)
       -> ((m -> Bool) -> [m] -> [m])
       -> (m -> Bool) -> Int

filter :: (m -> Bool) -> [m] -> [m]

length :: Foldable ((->) [m]) => ([m] -> [m]) -> Int

(.) length filter :: Foldable ((->) [m]) => (m -> Bool) -> Int

An easier way to understand why length . filter is not what you want is to look at the definition of (.).

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

Therefore:

(.) length filter
≡ \x -> length (filter x)

We know that filter x is not a list.


Pointless versions you may consider:

(length .) . filter

filter >=> return . length

(fmap.fmap) length filter

(id ~> id ~> length) filter -- [1]

filter $* id $$ id *$ length -- [2]

lurryA @N2 (length <$> (filter <$> _1 <*> _2)) -- [3]
  1. Control.Compose
  2. Data.Function.Meld
  3. Data.Function.Tacit
erisco
  • 14,154
  • 2
  • 40
  • 45
  • 1
    @WillNess thank-you for pointing out this mistake. I have made a correction. It should have read `filter >=> return . length`. – erisco Jun 22 '16 at 23:54
2

Using "three laws of operator sections", we have

((length .) . filter) x y =
 (length .) (filter x) y =
 (length . filter x) y =
 length (filter x y)

and

((length .) . filter) = 
  (.) (length .) filter =
  (.) ((.) length) filter =
  ((.) . (.)) length filter

The last bit, ((.).(.)), is sometimes known as "owl operator", also written as .: (length .: filter), or fmap . fmap (for functions, fmap is (.)).

Community
  • 1
  • 1
Will Ness
  • 70,110
  • 9
  • 98
  • 181