Home » Teaching topics » Functional Programming » Function composition

Subscribe to Blog via Email

Enter your email address to subscribe to this blog and receive notifications of new posts by email.

Join 8 other subscribers.

Function composition

Good functional programmers will construct functions that are modularised and pure so that they don’t produce any side effects. This means that functions will be limited in scope as to what they can (or perhaps shouldn’t) affect. They should handle small, simple tasks that, when called, produce individual results.

Obviously a program will not be limited if it can only produce snippets of useful outputs and results of different functions need to be combined to produce more useful outcomes. This concept is known as functional composition where the output of one function can be used as the input to another. What this is effectively doing is mapping an original domain to a co-domain that the function itself doesn’t map to. This example can be seen in the guidance for the AQA specification:

Given two functions

f: A → B
g: B → C

function g ○ f, called the composition of g and f, is a function whose domain is A and co-domain is C.

So the two functions combine to map the domain A to the co-domain C:

Function composition diagram
Diagram showing two composed functions

 

This can be considered in more detail by creating functions in Haskell. We can combine several functions to produce a more useful output than what the single original functions produce alone. The following example is based on predicting the success in a lottery of picking one of a selection of values from a pot of numbers.

In a lottery you pick a combination of numbers that you hope will be selected from the overall pot. The probability of selecting one number from a list of six values where there are 59 possible starting choices would be:

6 / 59 = 0.10169

or about a 10% chance

Creating this as a function in Haskell we can pass arguments specifying the number of choices and the pot of numbers to choose from (as above 6 and 59):

probOfSelect:: Int -> Int -> Double
probOfSelect a b = fromIntegral a / fromIntegral b

Perhaps a better way of representing this would be to identify how many times you would have to play in order to draw a number from your list of choices, so for our previous examples:

1 / 0.10169 = 9.83

which means you would have to play almost 10 times to successfully draw your number out of a pot of 59. Again we could create a function to work this out for us from the probability previously calculated:

playsToWin:: Double -> Double
playsToWin a = 1 / a

Function composition can be used to combine these functions in the same order. The number of lottery picks and the potential range of values can be used as arguments to determine the amount of draws that would need to take place on average to pick one of the list of values. Haskell uses the dot operator to combine functions, for example:

Prelude> let secondVal= head . tail
Prelude> secondVal [1,2,3,4]
2

The function tail is applied to the list and the result is then used as the argument for the head function. This composite function is called using a separate identifier secondVal. We can do the same with the probOfSelect and playsToWin functions:

lotteryChances a b = playsToWin . probOfSelect a $ b

The $ operator is used because the probOfSelect function requires two arguments. The dot operator takes precedence and will cause problems as it will only partially evaluate probOfSelect with one argument before trying to compose it with playsToWin. The $ forces the probOfSelect to fully evaluate first with both arguments before piping in the result to playsToWin. We can now call the new function with two arguments to achieve the desired result:

Prelude> lotteryChances 6 59
9.833333333333332

 


Leave a comment

Your email address will not be published. Required fields are marked *