Search code examples
functionf#function-compositionfirst-class-functions

F# function composition where the first function has arity >1


I have two functions f and g:

let f (x:float) (y:float) = 
    x * y

let g (x:float) = 
    x * 2.0

I want to compose (>>) them to get a new function that performs f and then g on the result.

The solution should behave like:

let h x y = 
    (f x y) |> g

This does not work:

// Does not compile
let h = 
    f >> g

How should >> be used?


Solution

  • Writing in point-free style, i.e. defining functions without explicit arguments, can become ugly when the implicit arguments are more than one.

    It can always be done, with the correct combination of operators. But the outcome is going to be a disappointment and you will lose the primary benefit of point-free style - simplicity and legibility.

    For fun and learning, we'll give it a try. Let's start with the explicit (a.k.a. "pointful") style and work from there.

    (Note: we're going to rearrange our composition operators into their explicit form (>>) (a) (b) rather than the more usual a >> b. This will create a bunch of parentheses, but it will make things easier to grok, without worrying about sometimes-unintuitive operator precedence rules.)

    let h x y =  f x y |> g
    
    let h x = f x >> g
    
    // everybody puts on their parentheses!
    let h x = (>>) (f x) (g)
    
    // swap order
    let h x = (<<) (g) (f x)
    
    // let's put another pair of parentheses around the partially applied function
    let h x = ((<<) g) (f x)
    

    There we are! See, now h x is expressed in the shape we want - "pass x to f, then pass the result to another function".

    That function happens to be ((<<) g), which is the function that takes a float -> float as argument and returns its composition with g.

    (A composition where g comes second, which is important, even if in the particular example used it doesn't make a difference.)

    Our float -> float argument is, of course, (f x) i.e. the partial application of f.

    So, the following compiles:

    let h x = x |> f |> ((<<) g)
    

    and that can now be quite clearly simplified to

    let h = f >> ((<<) g)
    

    Which isn't all that awful-looking, when you already know what it means. But anybody with any sense will much rather write and read let h x y = f x y |> g.