Function Techniques
Function Techniques
(a) A hammer
(b) A star-shaped screwdriver
(c) Pliers
(d) A flat-tipped screwdriver
(e) Bolt-cutters
Toolboxes
Imagine that you are a handyman.
If you have to count the number of characters in a string, what type will you use?
(a) A string
(b) A floating-point number
(c) Doubly-linked list
(d) An integer
(e) A null reference
Types
Let’s go back to being a programmer.
If you have to count the number of characters in a string, what type will you use?
(a) A string
(b) A floating-point number
Why? Because different types can be used
(c) Doubly-linked list
in different ways. That is totally normal—in
(d) An integer fact, that is a big part of the reason why we
(e) A null reference use different types in the first place!
Different types can do different things
● With an integer, you can use + and - and * and /
● With a floating point number, you can do all that and use **
● With a string, you can use +
● With a function, … 🤯?
A function is anything which
accepts one input value and,
when given that input, generates
one output value.
The type of a function is defined
by its input and output types:
typeinput → typeoutput
Functions: what are they
good for?
Helping out
Imagine that you are part of a team that is working on a project.
A member of the team doesn’t know how to do something, but you do.
The friend knows how to write things, but has never written a Politics essay before.
Let’s assume that there are two people, “Adam” and “Kim”. If Kim doesn’t know how to do a small part of a
bigger job, but Adam does, then Adam should go and do that small part for Kim.
This doesn’t mean that Kim is incompetent, or isn’t able to do the job. It just means that Adam can do a
small part of the job better. Maybe Adam has been researching in that area for many years, or maybe it is a
matter of taste and Adam is the one unique person in the world who happens to know exactly how that small
part of the job should be done.
We can say that for whatever reason, Adam is a specialist in doing that part of the job.
A specific problem
Let’s say that Adam asks Kim: “please add 1 to all these numbers”, and gives Kim a list of numbers.
If Adam asks Kim to “multiply these numbers by 5”, Kim can do that too.
What if Adam asks Kim: “please do something with each of these numbers”?
Very often, a “specialist” function will say how to do something, but not what to do.
In our example, a find function knows that it should find a value—this is what it should do.
But the other way of helping is by educating or training the other person, so that they can do the job
themselves.
Kim knows how to go through lists. Adam knows what to do with list items.
After being educated, we want Kim know how to do a specific thing (which Adam teaches Kim), with any list.
Interlude: back to being a handyman!
To fix a leak, our handyman might need many things:
…and at the end, the result is a fixed (non-leaking!) pipe. As long as we provide a handyman with all these
three things, we won’t have leaks.
We can say that these things are dependencies for the result. If one is missing, our house will be flooded.
After being educated, we want Kim know how to do a specific thing (which Adam teaches Kim), with any list.
That last bit (purple) is what is generated after the first two things are provided. Those first two things are
dependencies of the last thing.
Helping out, Part 2
('a -> 'b) -> List<'a> -> List<'b>
Usually, we provide this dependency first.
Helping out, Part 2
('a -> 'b) -> List<'a> -> List<'b>
We provide this dependency second.
Does the order of dependencies
matter?
🤯 No! 😲
Helping out, Part 2
('a -> 'b) -> List<'a> -> List<'b>
If we provide the first dependency only, we get:
Example code: map (fun x -> x + 2) or fun a -> map (fun x -> x + 2) a
A free symbol is used in a function, but doesn’t have a value bound to it—yet!
We turn our free symbols into a bound symbols through function application, and if the result is a function,
then we can say (informally) that it is a more “educated” function 😄!
Terminology: partial application
When we specify a part of a function’s inputs, and leave the rest of them to be specified later, that is like a
function technique called partial application*.
Intuitively, this is about building new functionality by bringing together functionality that we already have. It
is about teaching a “general” function how to do something more specific.
In our example, we would say that map has been partially applied.
* actually, partial application involves reducing the “arity” of a function too. But we’ll cover that later.
Finding closure
Terminology: arity
When you partially apply a function, the result is an “educated” function with fewer “dependencies”.
We say that a partially applied function has a smaller arity: a smaller number of “input slots”. The minimum
arity for a function is 1.
● Don’t confuse this with the “arity” of a tuple, which is the number of elements in the tuple
Terminology: closure
A bound symbol has been bound to a value.
A free symbol is used in a function, but doesn’t have a value bound to it—yet!
Imagine that you work in a shop. Somebody calls and says “I want to order a double-bed, and a sofa, and a
dressing-table”, and they give you all of the necessary information (name, delivery address, cellphone
number, payment details, etc etc)—but at the end of the call, they say:
“Just wait a bit, I want to check something. I might call back later to confirm.”
● Scenario 1. Your user has given you all the details about something, and when your program asks “Are
you sure you want to make these changes?”, the user says “Um—just wait a bit. I’m not sure.”
● Scenario 2. Some things are very computationally expensive to calculate. You want to delay
calculating them until you’re sure that they really need to be calculated, but you also want to
remember all the values that you’ll need to calculate them if it’s needed.
In each case, you want remember what to do , but also delay doing it until later on. How do we do that?
Delaying functions
What we need is some way to have an educated function that will execute later on, without requiring any
additional information. But we have a problem with this:
● A function is anything which accepts one input value and, when given that input, generates one
output value.
How do we make a function that can be given an input, but not given any additional information?!
🤔
Why do we use closures?
How do we make a function that can be given an input, but not given any additional information?
Instead of returning a value directly, we return a closure which calculates the value: fun () -> …
The () is a pattern which will only match the unit value ().
Until we call the function , passing it a unit value (), the body will not be executed!
Blowing your stack
(and how to stop)
Tail recursion
This is a low-level technique which does not affect the meaning of the program, but affects how it is
executed.
1. A recursive call is in tail position if nothing needs to be done after the recursive call.
2. A recursive function is tail-recursive if all recursive calls are tail recursive.
36
Tail recursion
Calls are in tail position if nothing needs to be done after the recursive call is complete.
When ALL recursive calls are in tail position, the function is tail recursive.
You can often convert a recursive function to a tail recursive function by adding an accumulator.
Tail recursion
You can often convert a recursive function to a tail recursive function by adding an accumulator.
NOTE: a recursive function without an accumulator can be tail recursive. A recursive function with an
accumulator might not be tail recursive. The important definition is the definition of tail position.
Joining functions
Joining pipes
Imagine a special kind of pipe that could be used in a chemical factory.
On the left side, something goes in. Something happens in the middle. Then on the right side, something
else goes out.
water hydrogen
A pipe doesn’t need to change what it takes in. Maybe it just heats it, for example:
water water
You can connect pipes together to get all kinds of outputs. But: if you connect a “hydrogen output” to a
“water input”, or a “iron output ” to a “copper input”, it won’t work!
Joining pipes
water ozone
>>
domain codomain
What happens to the first pipe’s codomain and the second pipe’s domain?
Composing functions
“Joining” two functions is called
“composition”.
● Recursive functions:
○ a function that repeats its behaviour, each time moving closer towards a solution
● Higher order functions:
○ a function that accepts another function, and relies on it to complete some task
○ a function that returns another function (e.g. teach)
● Partially applied functions:
○ a general function that has part of its input fixed, so that it becomes a more specific function
● Composed functions:
○ a new function created by joining together old functions
There are three more function-related items that we’ll cover: pipelining, backwards-pipelining, and currying.
Pipe convenience water
water hydrogen
In our programming language (F#)
water hydrogen
hydrogen
water hydrogen
In our programming language (F#)
If you think they improve readability—then use them! If you don’t—then don’t use them!
I’m only really covering them because they’re found in a lot of code: they are idiomatic in F# and you will see
them in many places, especially online.
Currying
A curried function is a function that returns another function.
Instead of e.g. fun (a, b) -> a + b , we write fun a b -> a + b or fun a -> fun b -> a + b .
Usually makes it easier to use all of the other techniques: recursion, partial application, composition,
pipelining, etc etc. All of these techniques tend to work better with a single input, rather than a group.
Functions
So far, we have picked up a lot of facts about the computational functions that we use:
1. Recursive functions:
○ a function that repeats its behaviour, each time moving closer towards a solution
2. Higher order functions:
○ a function that accepts another function, and relies on it to complete some task
○ a function that returns another function (e.g. teach)
3. Partially applied functions:
○ a general function that has part of its input fixed, so that it becomes a more specific function
4. Composed functions:
○ a new function created by joining together old functions
5. Curried functions:
○ a function that binds an input value and returns a function to bind the next input value
People classification
People can…
Some of these things are linked, but some are not! You need to look at the actual person to tell.
Function classification
Functions can be…
let rec f a b =
● Recursive
● Higher-order if a then
● Curried f false b
● Partially-applied else
● Composed (fun x -> x + 1) >> (fun n -> b * n)
● Lambda / named f true
● Total / partial
● …etc