The type Foldable t => t (b -> b) -> b -> b reads as:
- (
Foldable t => ...) Choose any list-like "container" type t,
- (
t (b -> b) -> ... ) then provide as an argument a t-container of functions b -> b,
- (
b -> b) the final result will be a function b -> b.
So, it's only slightly more general than: "give me a list of functions, and I will produce a function".
Indeed, when we use lists as containers:
foldr (.) id [f1,f2,f3,...,fn]
results, by definition of foldr, in
f1 . (f2 . (f3 . ... (fn . id) ...))
which is the composition of all the functions in the list.
So i thought that foldr (.) id should take a single parameter as input.
It does: the argument has type t (b -> b). Every function in Haskell takes a single parameter as input. E.g.
foo :: T -> U -> W -> Z
takes T and returns a function U -> W -> Z.
Now, we can also say that foo takes two arguments of type T and U and returns a function W -> Z. Or That it takes three arguments T, U, and W, and returns a Z. There is no real difference between these interpretations of a type, thanks to currying, so we can pick the one which is the easiest to grasp.
In your case, the result type of foldr (.) id is b -> b, so one usually interprets the first b as an additional argument. This does not provide a good intuition, though. It's easier to think of b -> b being the result type.
More technically: the type of foldr is (renaming variables for clarity).
foldr :: Foldable t => (a -> c -> c) -> c -> t a -> c
In foldr (.) id, we can see that the type of the second argument is id :: b -> b, hence we are using c = (b -> b), as if we specialized the above type to:
foldr :: Foldable t => (a -> (b -> b) -> (b -> b)) -> (b -> b) -> t a -> (b -> b)
Now, the first argument must have type (.) :: (a -> (b -> b) -> (b -> b)) to type check. This is possible only if a = (b -> b). Hence, we specialize again.
foldr :: Foldable t =>
((b -> b) -> (b -> b) -> (b -> b)) ->
(b -> b) ->
t (b -> b) ->
(b -> b)
which is the final type: after this specialization, foldr can then be applied to (.) and id.
All the specializations above are inferred automatically by GHC from your code. Essentially, GHC chooses a and c in the only way that can make your code type check