I have a hard time understanding what the function (:)(.) in haskell does. Could someone help me write it on pointful form, and explain step by step how to get there? The first step would be to expand the (:) so that we get ((.) :), but then I'm stuck.
It should be of type [(b->c)->(a->b)->a->c]->[(b->c)->(a->b)->a->c], but that doesn't help me, just makes me even more confused.
(:) (.)
Eta-expand:
\x -> (:) (.) x
Convert to infix notation:
\x -> (.) : x
I.e. x must be a list and we're prepending (.) to it (that's what : does: it prepends an element to a list).
(.) is a function, so x must be a list of functions.
(.) :: (b -> c) -> (a -> b) -> a -> c
, so x must have type
x :: [(b -> c) -> (a -> b) -> a -> c]
Well we can first convert the (:) data constructor, and the function (.) :: (b -> c) -> (a -> b) -> a -> c operator as a lambda expression:
(:) -> (\x y -> (x:y))
(.) -> (\f g t -> f (g t))
So that means that (:)(.) is short for:
(\x y -> (x:y)) (\f g t -> f (g t))
So now we can replace x with the lambda expression:
\y -> (\f g t -> f (g t)) : y
So the function is equal to ((.) :): a partial "cons" where we still need to fill in the tail, and the head is the dot operator.
So the type is a list of functions with the same signature as the dot operator [(b -> c) -> (a -> b) -> a -> c].
If we thus for example take as argument [], we have constructed a singleton list (a list with exactly one element): the dot operator.
Related
Say, we want to introduce the notion of sum of functions of different arguments (let's call it <+>), which behaves like the that: (f1 <+> f2)(x1, x2) == f1(x1) + f2(x2).
While this can be easily written out manually, it makes sense to use point-free style with the help of the notion of cartesian product of functions. The latter is defined below and seems alright and quite general to me:
x :: (x1 -> y1) -> (x2 -> y2) -> (x1 -> x2 -> (y1, y2))
x f1 f2 = \x1 x2 -> (f1(x1), f2(x2))
Then we can write:
(<+>):: Num a => (a -> a) -> (a -> a) -> (a -> a -> a)
(<+>) = (uncurry (+)) . x
And the code above seems fine to me too, but GHC thinks otherwise:
* Couldn't match type: (x20 -> y20) -> a -> x20 -> (a, y20)
with: ((a -> a) -> a -> a -> a, (a -> a) -> a -> a -> a)
Expected: (a -> a)
-> ((a -> a) -> a -> a -> a, (a -> a) -> a -> a -> a)
Actual: (a -> a) -> (x20 -> y20) -> a -> x20 -> (a, y20)
* Probable cause: `x' is applied to too few arguments
In the second argument of `(.)', namely `x'
In the expression: (uncurry (+)) . x
In an equation for `<+>': (<+>) = (uncurry (+)) . x
* Relevant bindings include
(<+>) :: (a -> a) -> (a -> a) -> a -> a -> a
It feels like the compiler cannot infer the second function's type, but why? And what am I supposed to do, is this even possible to do?
If you supply two arguments, you will see what has gone wrong.
(<+>) = uncurry (+) . x
(<+>) a = (uncurry (+) . x) a
= uncurry (+) (x a)
(<+>) a b = uncurry (+) (x a) b
Whoops! That b gets passed to uncurry as a third argument, rather than x as a second argument as you probably intended. The third and fourth arguments are also supposed to go to x rather than uncurry, as in:
(<+>) a b c d = uncurry (+) (x a b c d)
Here's the correct way to point-free-ify a four-argument composition.
\a b c d -> f (g a b c d)
= \a b c d -> (f . g a b c) d
= \a b c -> f . g a b c
= \a b c -> ((.) f . g a b) c
= \a b -> (.) f . g a b
= \a b -> ((.) ((.) f) . g a) b
= \a -> (.) ((.) f) . g a
= \a -> ((.) ((.) ((.) f)) . g) a
= (.) ((.) ((.) f)) . g
Most people then write this with section syntax as (((f .) .) .) . g. Applying this new fact to your case:
\a b c d -> uncurry (+) (x a b c d)
= (((uncurry (+) .) .) .) . x
The . operator is only for composing functions with a single argument, but the function x has four arguments, so you have to use . four times:
(<+>) = (((uncurry (+) .) .) .) . x
Do keep in mind that this is not considered good style in actual code.
Define
compose2 :: (b -> c -> t) -> (a -> b) -> (d -> c) -> a -> d -> t
compose2 p f g x y = p (f x) (g y)
Now, compose2 (+) is your <+>:
> :t compose2 (+)
compose2 (+) :: Num t => (a -> t) -> (d -> t) -> a -> d -> t
As you can see its type is a bit more general than you thought.
compose2 already exists.
I am trying to compose a function of type (Floating a) => a -> a -> a with a function of type (Floating a) => a -> a to obtain a function of type (Floating a) => a -> a -> a. I have the following code:
test1 :: (Floating a) => a -> a -> a
test1 x y = x
test2 :: (Floating a) => a -> a
test2 x = x
testBoth :: (Floating a) => a -> a -> a
testBoth = test2 . test1
--testBoth x y = test2 (test1 x y)
However, when I compile it in GHCI, I get the following error:
/path/test.hs:8:11:
Could not deduce (Floating (a -> a)) from the context (Floating a)
arising from a use of `test2'
at /path/test.hs:8:11-15
Possible fix:
add (Floating (a -> a)) to the context of
the type signature for `testBoth'
or add an instance declaration for (Floating (a -> a))
In the first argument of `(.)', namely `test2'
In the expression: test2 . test1
In the definition of `testBoth': testBoth = test2 . test1
Failed, modules loaded: none.
Note that the commented-out version of testBoth compiles. The strange thing is that if I remove the (Floating a) constraints from all type signatures or if I change test1 to just take x instead of x and y, testBoth compiles.
I've searched StackOverflow, Haskell wikis, Google, etc. and not found anything about a restriction on function composition relevant to this particular situation. Does anyone know why this is happening?
\x y -> test2 (test1 x y)
== \x y -> test2 ((test1 x) y)
== \x y -> (test2 . (test1 x)) y
== \x -> test2 . (test1 x)
== \x -> (test2 .) (test1 x)
== \x -> ((test2 .) . test1) x
== (test2 .) . test1
These two things are not like each other.
test2 . test1
== \x -> (test2 . test1) x
== \x -> test2 (test1 x)
== \x y -> (test2 (test1 x)) y
== \x y -> test2 (test1 x) y
You're problem doesn't have anything to do with Floating, though the typeclass does make your error harder to understand. Take the below code as an example:
test1 :: Int -> Char -> Int
test1 = undefined
test2 :: Int -> Int
test2 x = undefined
testBoth = test2 . test1
What is the type of testBoth? Well, we take the type of (.) :: (b -> c) -> (a -> b) -> a -> c and turn the crank to get:
b ~ Int (the argument of test2 unified with the first argument of (.))
c ~ Int (the result of test2 unified with the result of the first argument of (.))
a ~ Int (test1 argument 1 unified with argument 2 of (.))
b ~ Char -> Int (result of test1 unified with argument 2 of (.))
but wait! that type variable, 'b' (#4, Char -> Int), has to unify with the argument type of test2 (#1, Int). Oh No!
How should you do this? A correct solution is:
testBoth x = test2 . test1 x
There are other ways, but I consider this the most readable.
Edit: So what was the error trying to tell you? It was saying that unifying Floating a => a -> a with Floating b => b requires an instance Floating (a -> a) ... while that's true, you really didn't want GHC to try and treat a function as a floating point number.
Your problem has nothing to do with Floating, but with the fact that you want to compose a function with two arguments and a function with one argument in a way that doesn't typecheck. I'll give you an example in terms of a composed function reverse . foldr (:) [].
reverse . foldr (:) [] has the type [a] -> [a] and works as expected: it returns a reversed list (foldr (:) [] is essentially id for lists).
However reverse . foldr (:) doesn't type check. Why?
When types match for function composition
Let's review some types:
reverse :: [a] -> [a]
foldr (:) :: [a] -> [a] -> [a]
foldr (:) [] :: [a] -> [a]
(.) :: (b -> c) -> (a -> b) -> a -> c
reverse . foldr (:) [] typechecks, because (.) instantiates to:
(.) :: ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
In other words, in type annotation for (.):
a becomes [a]
b becomes [a]
c becomes [a]
So reverse . foldr (:) [] has the type [a] -> [a].
When types don't match for function composition
reverse . foldr (:) doesn't type check though, because:
foldr (:) :: [a] -> [a] -> [a]
Being the right operant of (.), it would instantiate its type from a -> b to [a] -> ([a] -> [a]). That is, in:
(b -> c) -> (a -> b) -> a -> c
Type variable a would be replaced with [a]
Type variable b would be replaced with [a] -> [a].
If type of foldr (:) was a -> b, the type of (. foldr (:)) would be:
(b -> c) -> a -> c`
(foldr (:) is applied as a right operant to (.)).
But because type of foldr (:) is [a] -> ([a] -> [a]), the type of (. foldr (:)) is:
(([a] -> [a]) -> c) -> [a] -> c
reverse . foldr (:) doesn't type check, because reverse has the type [a] -> [a], not ([a] -> [a]) -> c!
Owl operator
When people first learn function composition in Haskell, they learn that when you have the last argument of function at the right-most of the function body, you can drop it both from arguments and from the body, replacing or parentheses (or dollar-signs) with dots. In other words, the below 4 function definitions are equivalent:
f a x xs = g ( h a ( i x xs))
f a x xs = g $ h a $ i x xs
f a x xs = g . h a . i x $ xs
f a x = g . h a . i x
So people get an intuition that says “I just remove the right-most local variable from the body and from the arguments”, but this intuition is faulty, because once you removed xs,
f a x = g . h a . i x
f a = g . h a . i
are not equivalent! You should understand when function composition typechecks and when it doesn't. If the above 2 were equivalent, then it would mean that the below 2 are also equivalent:
f a x xs = g . h a . i x $ xs
f a x xs = g . h a . i $ x xs
which makes no sense, because x is not a function with xs as a parameter. x is a parameter to function i, and xs is a parameter to function (i x).
There is a trick to make a function with 2 parameters point-free. And that is to use an “owl” operator:
f a x xs = g . h a . i x xs
f a = g . h a .: i
where (.:) = (.).(.)
The above two function definitions are equivalent. Read more on “owl” operator.
References
Haskell programming becomes much easier and straightforward, once you understand functions, types, partial application and currying, function composition and dollar-operator. To nail these concepts, read the following StackOverflow answers:
On types and function composition
On higher-order functions, currying, and function composition
On Haskell type system
On point-free style
On const
On const, flip and types
On curry and uncurry
Read also:
Haskell: difference between . (dot) and $ (dollar sign)
Haskell function composition (.) and function application ($) idioms: correct use
I believe I understand fmap . fmap for Functors, but on functions it's hurting my head for months now.
I've seen that you can just apply the definition of (.) to (.) . (.), but I've forgot how to do that.
When I try it myself it always turns out wrong:
(.) f g = \x -> f (g x)
(.) (.) (.) = \x -> (.) ((.) x)
\x f -> (.) ((.) x) f
\x f y -> (((.)(f y)) x)
\x f y g-> (((.)(f y) g) x)
\x f y g-> ((f (g y)) x)
\x f y g-> ((f (g y)) x):: t2 -> (t1 -> t2 -> t) -> t3 -> (t3 -> t1) -> t
If "just applying the definition" is the only way of doing it, how did anybody come up with (.) . (.)?
There must be some deeper understanding or intuition I'm missing.
Coming up with (.) . (.) is actually pretty straightforward, it's the intuition behind what it does that is quite tricky to understand.
(.) gets you very far when rewriting expression into the "pipe" style computations (think of | in shell). However, it becomes awkward to use once you try to compose a function that takes multiple arguments with a function that only takes one. As an example, let's have a definition of concatMap:
concatMap :: (a -> [b]) -> [a] -> [b]
concatMap f xs = concat (map f xs)
Getting rid of xs is just a standard operation:
concatMap f = concat . map f
However, there's no "nice" way of getting rid of f. This is caused by the fact, that map takes two arguments and we'd like to apply concat on its final result.
You can of course apply some pointfree tricks and get away with just (.):
concatMap f = (.) concat (map f)
concatMap f = (.) concat . map $ f
concatMap = (.) concat . map
concatMap = (concat .) . map
But alas, readability of this code is mostly gone. Instead, we introduce a new combinator, that does exactly what we need: apply the second function to the final result of first one.
-- .: is fairly standard name for this combinator
(.:) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(f .: g) x y = f (g x y)
concatMap = concat .: map
Fine, that's it for motivation. Let's get to the pointfree business.
(.:) = \f g x y -> f (g x y)
= \f g x y -> f ((g x) y)
= \f g x y -> f . g x $ y
= \f g x -> f . g x
Now, here comes the interesting part. This is yet another of the pointfree tricks that usually helps when you get stuck: we rewrite . into its prefix form and try to continue from there.
= \f g x -> (.) f (g x)
= \f g x -> (.) f . g $ x
= \f g -> (.) f . g
= \f g -> (.) ((.) f) g
= \f -> (.) ((.) f)
= \f -> (.) . (.) $ f
= (.) . (.)
As for intuition, there's this very nice article that you should read. I'll paraphrase the part about (.):
Let's think again about what our combinator should do: it should apply f to the result of result of g (I've been using final result in the part before on purpose, it's really what you get when you fully apply - modulo unifying type variables with another function type - the g function, result here is just application g x for some x).
What it means for us to apply f to the result of g? Well, once we apply g to some value, we'll take the result and apply f to it. Sounds familiar: that's what (.) does.
result :: (b -> c) -> ((a -> b) -> (a -> c))
result = (.)
Now, it turns out that composition (our of word) of those combinators is just a function composition, that is:
(.:) = result . result -- the result of result
You can also use your understanding of fmap . fmap.
If you have two Functors foo and bar, then
fmap . fmap :: (a -> b) -> foo (bar a) -> foo (bar b)
fmap . fmap takes a function and produces an induced function for the composition of the two Functors.
Now, for any type t, (->) t is a Functor, and the fmap for that Functor is (.).
So (.) . (.) is fmap . fmap for the case where the two Functors are (->) s and (->) t, and thus
(.) . (.) :: (a -> b) -> ((->) s) ((->) t a) -> ((->) s) ((->) t b)
= (a -> b) -> (s -> (t -> a)) -> (s -> (t -> b))
= (a -> b) -> (s -> t -> a ) -> (s -> t -> b )
it "composes" a function f :: a -> b with a function of two arguments, g :: s -> t -> a,
((.) . (.)) f g = \x y -> f (g x y)
That view also makes it clear that, and how, the pattern extends to functions taking more arguments,
(.) :: (a -> b) -> (s -> a) -> (s -> b)
(.) . (.) :: (a -> b) -> (s -> t -> a) -> (s -> t -> b)
(.) . (.) . (.) :: (a -> b) -> (s -> t -> u -> a) -> (s -> t -> u -> b)
etc.
Your solution diverges when you introduce y. It should be
\x f y -> ((.) ((.) x) f) y :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> ((.) ((.) x) f) y z :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> ((.) x (f y)) z :: (c -> d) -> (a -> b -> c) -> a -> b -> d
-- Or alternately:
\x f y z -> (x . f y) z :: (c -> d) -> (a -> b -> c) -> a -> b -> d
\x f y z -> (x (f y z)) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
Which matches the original type signature: (.) . (.) :: (c -> d) -> (a -> b -> c) -> a -> b -> d
(It's easiest to do the expansion in ghci, where you can check each step with :t expression)
Edit:
The deeper intution is this:
(.) is simply defined as
\f g -> \x -> f (g x)
Which we can simplify to
\f g x -> f (g x)
So when you supply it two arguments, it's curried and still needs another argument to resolve.
Each time you use (.) with 2 arguments, you create a "need" for one more argument.
(.) . (.) is of course just (.) (.) (.), so let's expand it:
(\f0 g0 x0 -> f0 (g0 x0)) (\f1 g1 x1 -> f1 (g1 x1)) (\f2 g2 x2 -> f2 (g2 x2))
We can beta-reduce on f0 and g0 (but we don't have an x0!):
\x0 -> (\f1 g1 x1 -> f1 (g1 x1)) ((\f2 g2 x2 -> f2 (g2 x2)) x0)
Substitute the 2nd expression for f1...
\x0 -> \g1 x1 -> ((\f2 g2 x2 -> f2 (g2 x2)) x0) (g1 x1)
Now it "flips back"! (beta-reduction on f2):
This is the interesting step - x0 is substituted for f2 -- This means that x, which could have been data, is instead a function.
This is what (.) . (.) provides -- the "need" for the extra argument.
\x0 -> \g1 x1 -> (\g2 x2 -> x0 (g2 x2)) (g1 x1)
This is starting to look normal...
Let's beta-reduce a last time (on g2):
\x0 -> \g1 x1 -> (\x2 -> x0 ((g1 x1) x2))
So we're left with simply
\x0 g1 x1 x2 -> x0 ((g1 x1) x2)
, where the arguments are nicely still in order.
So, this is what I get when I do a slightly more incremental expansion
(.) f g = \x -> f (g x)
(.) . g = \x -> (.) (g x)
= \x -> \y -> (.) (g x) y
= \x -> \y -> \z -> (g x) (y z)
= \x y z -> (g x) (y z)
(.) . (.) = \x y z -> ((.) x) (y z)
= \x y z -> \k -> x (y z k)
= \x y z k -> x (y z k)
Which, according to ghci has the correct type
Prelude> :t (.) . (.)
(.) . (.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
Prelude> :t \x y z k -> x (y z k)
\x y z k -> x (y z k)
:: (t1 -> t) -> (t2 -> t3 -> t1) -> t2 -> t3 -> t
Prelude>
While I don't know the origins of this combinator, it is likely that it was
developed for use in combinatory logic, where you work strictly with combinators,
so you can't define things using more convenient lambda expressions. There may be
some intuition that goes with figuring these things out, but I haven't found it.
Most likely, you would develop some level of intuition if you had to do it enough.
It's easiest to write equations, combinators-style, instead of lambda-expressions: a b c = (\x -> ... body ...) is equivalent to a b c x = ... body ..., and vice versa, provided that x does not appear among {a,b,c}. So,
-- _B = (.)
_B f g x = f (g x)
_B _B _B f g x y = _B (_B f) g x y
= (_B f) (g x) y
= _B f (g x) y
= f ((g x) y)
= f (g x y)
You discover this if, given f (g x y), you want to convert it into a combinatory form (get rid of all the parentheses and variable repetitions). Then you apply patterns corresponding to the combinators' definitions, hopefully tracing this derivation backwards. This is much less mechanical/automatic though.
This question already has answers here:
What does $ mean/do in Haskell?
(2 answers)
Closed 6 years ago.
I'm having trouble with understanding how function application works with currying in haskell.
If I have following function:
($) :: (a -> b) -> a -> b
I understand that to partially apply this function I need to provide (a -> b) function ($'s first argument).
Why then is it possible to apply a value first (i.e. reverse arguments)?
($ 0) :: Num a => (a -> b) -> b
What am I missing here?
($) is an operator. In Haskell, any operator can be written in a left-section (like (x $)) or a right-section (like ($ x)):
(x $) = (\y -> x $ y) = ($) x
($ x) = (\y -> y $ x) = flip ($) x
Note that the only exception to this rule is (-), in order to conveniently write negative numbers:
\x -> (x-) :: Num a => a -> a -> a -- equivalent to \x -> (-) x
\x -> (-x) :: Num a => a -> a -- equivalent to \x -> negate x
In case you want to tersely write (\y -> y - x), you can use subtract:
\x -> subtract x :: Num a => a -> a -> a -- equivalent to \x -> flip (-) x
($ 0) ≡ (\x -> x $ 0) ≡ (\x -> ($) x 0)
If ($) :: (a -> b) -> a -> b) and we applied second argument like (\x -> ($) x 0) we have :: Num a => (a -> b) -> b
You are confusing the infix notation of an operator with a function.
> :t (($) (+1))
(($) (+1)) :: Num b => b -> b
Here are some forms of expressions with $, for better understanding:
a $ b => ($) a b
($ b) => flip ($) b => (\b a -> ($) a b) b => \a -> ($) a b
(a $) => ($) a => \b -> ($) a b
Note also that in Haskell syntax, alphanumeric names are distinguished from punctuation names.
An alphanumeric function foo1 a b is prefix by default, and becomes infix if you add backticks: a `foo` b.
A punctuation-named function like $ or <*> is infix by default, and becomes prefix if you add parentheses ($) or (<*>). This is just syntax sugar for the programmer familiar with the Latin alphabet; it is an arbitrary but helpful distinction between alphanumeric names and punctuation names.
Both kinds of functions are just functions, they don't have the special semantic rules that we have for "operators" in C++ or Java. It's just the syntax rules around prefix/infix and backticks/parentheses that are different between punctuation-named functions and alphanumeric-named functions.
I am trying to compose a function of type (Floating a) => a -> a -> a with a function of type (Floating a) => a -> a to obtain a function of type (Floating a) => a -> a -> a. I have the following code:
test1 :: (Floating a) => a -> a -> a
test1 x y = x
test2 :: (Floating a) => a -> a
test2 x = x
testBoth :: (Floating a) => a -> a -> a
testBoth = test2 . test1
--testBoth x y = test2 (test1 x y)
However, when I compile it in GHCI, I get the following error:
/path/test.hs:8:11:
Could not deduce (Floating (a -> a)) from the context (Floating a)
arising from a use of `test2'
at /path/test.hs:8:11-15
Possible fix:
add (Floating (a -> a)) to the context of
the type signature for `testBoth'
or add an instance declaration for (Floating (a -> a))
In the first argument of `(.)', namely `test2'
In the expression: test2 . test1
In the definition of `testBoth': testBoth = test2 . test1
Failed, modules loaded: none.
Note that the commented-out version of testBoth compiles. The strange thing is that if I remove the (Floating a) constraints from all type signatures or if I change test1 to just take x instead of x and y, testBoth compiles.
I've searched StackOverflow, Haskell wikis, Google, etc. and not found anything about a restriction on function composition relevant to this particular situation. Does anyone know why this is happening?
\x y -> test2 (test1 x y)
== \x y -> test2 ((test1 x) y)
== \x y -> (test2 . (test1 x)) y
== \x -> test2 . (test1 x)
== \x -> (test2 .) (test1 x)
== \x -> ((test2 .) . test1) x
== (test2 .) . test1
These two things are not like each other.
test2 . test1
== \x -> (test2 . test1) x
== \x -> test2 (test1 x)
== \x y -> (test2 (test1 x)) y
== \x y -> test2 (test1 x) y
You're problem doesn't have anything to do with Floating, though the typeclass does make your error harder to understand. Take the below code as an example:
test1 :: Int -> Char -> Int
test1 = undefined
test2 :: Int -> Int
test2 x = undefined
testBoth = test2 . test1
What is the type of testBoth? Well, we take the type of (.) :: (b -> c) -> (a -> b) -> a -> c and turn the crank to get:
b ~ Int (the argument of test2 unified with the first argument of (.))
c ~ Int (the result of test2 unified with the result of the first argument of (.))
a ~ Int (test1 argument 1 unified with argument 2 of (.))
b ~ Char -> Int (result of test1 unified with argument 2 of (.))
but wait! that type variable, 'b' (#4, Char -> Int), has to unify with the argument type of test2 (#1, Int). Oh No!
How should you do this? A correct solution is:
testBoth x = test2 . test1 x
There are other ways, but I consider this the most readable.
Edit: So what was the error trying to tell you? It was saying that unifying Floating a => a -> a with Floating b => b requires an instance Floating (a -> a) ... while that's true, you really didn't want GHC to try and treat a function as a floating point number.
Your problem has nothing to do with Floating, but with the fact that you want to compose a function with two arguments and a function with one argument in a way that doesn't typecheck. I'll give you an example in terms of a composed function reverse . foldr (:) [].
reverse . foldr (:) [] has the type [a] -> [a] and works as expected: it returns a reversed list (foldr (:) [] is essentially id for lists).
However reverse . foldr (:) doesn't type check. Why?
When types match for function composition
Let's review some types:
reverse :: [a] -> [a]
foldr (:) :: [a] -> [a] -> [a]
foldr (:) [] :: [a] -> [a]
(.) :: (b -> c) -> (a -> b) -> a -> c
reverse . foldr (:) [] typechecks, because (.) instantiates to:
(.) :: ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
In other words, in type annotation for (.):
a becomes [a]
b becomes [a]
c becomes [a]
So reverse . foldr (:) [] has the type [a] -> [a].
When types don't match for function composition
reverse . foldr (:) doesn't type check though, because:
foldr (:) :: [a] -> [a] -> [a]
Being the right operant of (.), it would instantiate its type from a -> b to [a] -> ([a] -> [a]). That is, in:
(b -> c) -> (a -> b) -> a -> c
Type variable a would be replaced with [a]
Type variable b would be replaced with [a] -> [a].
If type of foldr (:) was a -> b, the type of (. foldr (:)) would be:
(b -> c) -> a -> c`
(foldr (:) is applied as a right operant to (.)).
But because type of foldr (:) is [a] -> ([a] -> [a]), the type of (. foldr (:)) is:
(([a] -> [a]) -> c) -> [a] -> c
reverse . foldr (:) doesn't type check, because reverse has the type [a] -> [a], not ([a] -> [a]) -> c!
Owl operator
When people first learn function composition in Haskell, they learn that when you have the last argument of function at the right-most of the function body, you can drop it both from arguments and from the body, replacing or parentheses (or dollar-signs) with dots. In other words, the below 4 function definitions are equivalent:
f a x xs = g ( h a ( i x xs))
f a x xs = g $ h a $ i x xs
f a x xs = g . h a . i x $ xs
f a x = g . h a . i x
So people get an intuition that says “I just remove the right-most local variable from the body and from the arguments”, but this intuition is faulty, because once you removed xs,
f a x = g . h a . i x
f a = g . h a . i
are not equivalent! You should understand when function composition typechecks and when it doesn't. If the above 2 were equivalent, then it would mean that the below 2 are also equivalent:
f a x xs = g . h a . i x $ xs
f a x xs = g . h a . i $ x xs
which makes no sense, because x is not a function with xs as a parameter. x is a parameter to function i, and xs is a parameter to function (i x).
There is a trick to make a function with 2 parameters point-free. And that is to use an “owl” operator:
f a x xs = g . h a . i x xs
f a = g . h a .: i
where (.:) = (.).(.)
The above two function definitions are equivalent. Read more on “owl” operator.
References
Haskell programming becomes much easier and straightforward, once you understand functions, types, partial application and currying, function composition and dollar-operator. To nail these concepts, read the following StackOverflow answers:
On types and function composition
On higher-order functions, currying, and function composition
On Haskell type system
On point-free style
On const
On const, flip and types
On curry and uncurry
Read also:
Haskell: difference between . (dot) and $ (dollar sign)
Haskell function composition (.) and function application ($) idioms: correct use