I've read that the composition of g :: A -> B and f :: B -> C, pronounced (“f composed of g”), results in another function (arrow) from A -> C. This can be expressed more formally as
f • g = f(g) = compose :: (B -> C) -> (A -> B) -> (A -> C)
Can the above composition be also defined as below? Please clarify. In this case the compose function takes the same two functions f and g and return a new function from A -> C.
f • g = f(g) = compose :: ((B -> C), (A -> B)) -> (A -> C)
First we need to get some things right:
f ○ g
means something quite different from f(g)
.
x
, will first feed it to g
, then pass on the result to f
, and output that final result, i.e. f(g(x))
.f(g)
means you apply the function f
to the value g
right away, without waiting for any argument. (g
just happens to have a function type, but in functional languages, functions can be passed around just like any other values / arguments).Unless you're dealing with some pretty wacky polymorphic functions, one of these will be ill-typed. For example, a well-typed composition might be
sqrt ○ abs :: Double -> Double
whereas a well-typed application could be (at least in Haskell)
map(sqrt) :: [Double] -> [Double]
I'll assume in the following you're talking about f ○ g
.
Type signatures must be given for a function itself, not for a function applied to some arguments. This is something that loads of people get utterly wrong: in f(x)
, you have a function f
and an argument x
. But f(x)
is not a function, only the value that's the result of applying a function to a value! So, you shouldn't write something like f ○ g :: ...
(unless you're actually talking only about the type that results from the composition). Better write just ○ :: ...
(or, in Haskell, (○) :: ...
).
Function arrows aren't associative. Most mathematicians likely won't even know what X -> Y -> Z
is supposed to mean. What it means in languages like Haskell may actually be somewhat surprising:
X -> Y -> Z ≡ X -> (Y -> Z)
i.e. this is the type of a function that first takes only an argument of type X
. The result will be again a function, but one that takes only an argument of type Y
. This function will have, if you like, the X
value already built-in (in a so-called closure, unless the compiler optimises that away). Giving it also the Y
value will allow the function to actually do its job and finally yield the Z
result.
At this point you already have your answer, pretty much: indeed the signatures X -> Y -> Z
and (X, Y) -> Z
are essentially equivalent. The process of rewriting this is called currying.
To answer your question in particular: most languages don't normally do any currying, so the signature ((B -> C), (A -> B)) -> (A -> C)
is actually more correct. It corresponds to a function you can call as
compose(f,g)
OTOH, the curried signature (B -> C) -> (A -> B) -> (A -> C)
means that you need to feed in the arguments one by one:
compose(f)(g)
Only in languages like Haskell is this the standard style, but you don't need the parens there: all the following are parsed the same in Haskell
compose(f)(g)
compose f g
(compose) f g
(.) f g
f . g
where .
is in fact the composition operator, which as you can see from the documentation has type
(.) :: (b -> c) -> (a -> b) -> a -> c