Composition
Example of Composition
It's common to store function return values into temp variables and then pass that into other functions.
You may have manually performed composition if you refactored this code to remove the temp variables, instead passing your functions directly as arguments to other functions.
We can improve this code even more though. We can abstract the calculation of the shipping rate into a shippingRate
function.
Pro tip: Abstraction is valuable because it creates a semantic boundary around your code. It allows you to reason about some section of your code independently of the rest.
Declarative Data Flow
In our example, suppose that we need to calculate different kinds of shipping rates: international, national, local, etc.
There is a pattern in each of these shipping rate calculations. They each take in an input and pass them into a bunch of composed functions. The functions themselves may change, but the act of composition remains the same.
So we can create a compose
helper function to handle all the different shipping rates:
This act of composition is a form of declarative data flow. Your shipping rate functions get defined declaratively and point-free, and it's really easy to read that your inputs (the data) get passed from right to left in a series of functions found in composeThree
.
As a metaphor, think of compose
as a machine-making machine. It spits out as many factory lines as you want, each one taking inputs, running them through a series of functions, and spitting out outputs.
Piping vs. Composition
Compose is right to left. Pipe is left to right.
Here's an example of pipe and how it might look behind the scenes:
Pro tip: Compose might feel more unintuitive at first (probably because we read left to right), but it's actually a better way to approach functional programming.
Associativity
Associativity is a property of an operation where rearranging the parentheses in an expression does not change the result.
Composition is associative.
Why it matters: If composition is associative, it means we can perform currying and partial application alongside it!
In other words, you don't have to know all the functions that are going to participate in a composition upfront. You can curry
the compose
utility and use the result to compose it with whatever you want later.
Composition with Currying
The natural way to compose a series of functions is for them all to be unary. After all, it's easier to pass the output of one function into the input of another when the input expected is always unary.
So how can you compose functions that are not unary? You can curry them, turning them into unary functions.
Last updated