Search code examples
javascripthaskellfunctional-programmingcompositionfunction-composition

Compose function signature


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)


Solution

  • First we need to get some things right:

    • f ○ g means something quite different from f(g).

      • The former is a function that, given an argument x, will first feed it to g, then pass on the result to f, and output that final result, i.e. f(g(x)).
      • OTOH, 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