0% found this document useful (0 votes)
10 views58 pages

Function Techniques

The document discusses the concept of functions in programming, emphasizing their utility and the different types of functions, including higher-order functions and specialist functions. It explains how functions can be composed, partially applied, and how closures can be used to delay execution. The document also touches on the importance of understanding dependencies and the execution of recursive functions, particularly tail recursion.

Uploaded by

venimkoki1
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
10 views58 pages

Function Techniques

The document discusses the concept of functions in programming, emphasizing their utility and the different types of functions, including higher-order functions and specialist functions. It explains how functions can be composed, partially applied, and how closures can be used to delay execution. The document also touches on the importance of understanding dependencies and the execution of recursive functions, particularly tail recursion.

Uploaded by

venimkoki1
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 58

Function Techniques

“Just Functions?” What can you do with “just functions”??!


Toolboxes
Imagine that you are a handyman.

If you have to screw in a screw like →


what will you use?

(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 screw in a screw like →


what will you use? Why? Because different tools can be used
in different ways. That is totally normal—in
(a) A hammer fact, that is a big part of the reason why we
(b) A star-shaped screwdriver make different tools in the first place!
(c) Pliers
(d) A flat-tipped screwdriver
(e) Bolt-cutters
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
(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.

What do you do?


Helping out
Imagine that a lecturer wants your friend to write an essay for Politics.

The friend knows how to write things, but has never written a Politics essay before.

What do you do?


Helping out
The ideas of sending help and getting help are as old as humanity. We all need help sometimes, and we all
help other people sometimes. This idea is in all religions, all societies, and all ethical systems.

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.

Kim can do that.

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”?

Can we write a function that would satisfy Adam?


“Specialist” functions
In programming—and in mathematics—we often want to use “specialist” functions. For example, when
determining which value we want in a sequence, we can pass a “specialist” function that identifies the value.

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.

A “specialist function” tells it how to find the value.


Terminology: higher order function
If a function accepts or returns a “specialist” function, then that function is called a higher order function.

find, filter, map, etc are all higher order functions.

(in case you are wondering, there is no agreed-upon name for a


“specialist” function! Some sources call them callback
functions, but that isn’t a well-established name—yet.)
Helping out, Part 2
When we need to “give help” to a higher order function, we can send a “specialist” function to it, and that
“specialist” function can help the higher order function to do its work.

But the other way of helping is by educating or training the other person, so that they can do the job
themselves.

Let’s return to our example of people helping each other.

Kim knows how to go through lists. Adam knows what to do with list items.

If Adam educates Kim about how to “do something”, then


Kim will be able to always remember and do it—for any list!
Helping out, Part 2
Initially, Kim knows how to go through a list, but not how to do anything in particular with the items in that
list.

let rec map doSomething = What Adam


function knows
| [] -> [] What Kim
| v::others -> knows
(doSomething v) :: map doSomething others
(fun v -> v + 2)

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:

● Knowledge of how to use a wrench to fix the leak.


● A wrench.
● Plumbers’ tape.

…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.

(this is a terrible simplification, please don’t show it to any actual plumbers! 󰚣)


Helping out, Part 2 let rec map doSomething =
function
| [] -> []
Initially, Kim knows how to go through a list, but not | v::others ->
how to do anything in particular with the items in that (doSomething v) :: map doSomething others
list.

After being educated, we want Kim know how to do a specific thing (which Adam teaches Kim), with any list.

Let’s look at the type of the higher-order function.

('a -> 'b) -> (List<'a> -> List<'b>)

But we usually write this with fewer brackets, as:

('a -> 'b) -> List<'a> -> List<'b>


Helping out, Part 2
('a -> 'b) -> List<'a> -> List<'b>
Let’s understand this in terms of “what is needed to construct something else”—in other words, in terms of
dependencies.

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:

List<'a> -> List<'b>


If we provide the second dependency only, we get:

('a -> 'b) -> List<'b>


What does this mean in terms of “Adam” and “Kim”?
Helping out, Part 2
('a -> 'b) -> List<'a> -> List<'b>
If we provide the first dependency only, we get:

List<'a> -> List<'b>

Example code: map (fun x -> x + 2) or fun a -> map (fun x -> x + 2) a

If we provide the second dependency only, we get:

('a -> 'b) -> List<'b>

Example code: fun f -> map f [5;6;7;2]


Bound and free variables
When looking at things like this, a concept that might be useful is that of bound and free variables.

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!

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!

A closure is a function value that contains one or more bound symbols.


Why do we use closures?
Closures let us make new functions that have some kind of education. But they can also serve another
purpose.

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.”

…what do you do?


Delaying functions
Closures let us make new functions that have some kind of education. But they can also serve another
purpose.

Let’s go back to being software developers.

● 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?

We use a unit value, which cannot pass along any 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.

⚠ DO NOT WORRY ABOUT TAIL RECURSION


⚠ until/unless you have a working function!
Tail recursion affects the way in which a program is executed. The following definitions are be important:

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 oxygen oxygen ozone

water ozone

What happened to the oxygen?


Joining functions
Just like our magical chemistry pipes,
functions also have a “kind of input”
and a “kind of output”: the domain and
codomain.

Just like our magical chemistry pipes,


we can “join” functions together.

This does not change the original


functions: it creates a new function!
(5+1=6 does not change “5” or “1”: it
creates a new value “6”!)
“Joining pipes”

domain codomain domain codomain

>>

domain codomain

What happens to the first pipe’s codomain and the second pipe’s domain?
Composing functions
“Joining” two functions is called
“composition”.

In our programming language, we can


compose two functions f and g by writing:
>>
f >> g
The result is a new function which does f
and then does g.

It is like joining two “function pipes”


together to make a new single pipe!
Composing functions
“Joining” two functions in reverse order is
called “backwards composition”.

In our programming language, we can


backwards-compose two functions f and g
by writing:
<<
f << g
The result is a new function which does g
and then does f.

It is like joining two “function pipes”


together to make a new single pipe!
New functions from old functions
The general idea of “making new functions that are based on old functions” is incredibly powerful—in fact, it
is the basis of many techniques in functional programming! We’ve seen it in these forms:

● 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

Let’s talk about the chemistry pipes a bit more.

Because it’s sometimes just easier, we might want to just want


to drop a chemical in from above a pipe, rather than feed it
directly through the “input” side. hydrogen

As you can see, this doesn’t actually change anything! It just


might be easier for a factory worker to set up.

water hydrogen
In our programming language (F#)

(fun x -> x * 2) (5+1) 5+1 |> (fun x -> x * 2)

“Usual” syntax of F# “Pipeline” syntax of F#


● The input is written after the function ● The input is written before the function
● If the input is created by an expression, use ● The “|>” pipe-operator is used
brackets around the expression ● No brackets are needed if the input is
created by an expression
“Chemistry pipes”
water

water hydrogen

hydrogen

“Usual” pipe arrangement “Pipeline” pipe arrangement


● The input is given on the “input” side ● The input is poured in on the “input” side
● The attachment is used
Pipe convenience
water

Or we might want to feed in a chemical from a different


hydrogen
level of the factory.

Just as before, this doesn’t actually change anything! It just


might be easier for a factory worker to set up.

water hydrogen
In our programming language (F#)

(fun x -> x * 2) (5+1) (fun x -> x * 2) <| 5+1

“Usual” syntax of F# “Backwards-Pipeline” syntax of F#


● If the input is created by an expression, use ● The “<|” pipe-operator is used
brackets around the expression ● No brackets are needed if the input is created
by an expression
“Chemistry pipes”
water

water hydrogen hydrogen

“Usual” pipe arrangement “Pipeline” pipe arrangement


● The input is given on the “input” side ● The input is poured in from a higher level on
the “input” side
● The attachment is used
“Pipeline” and “Backwards-pipeline”
The “|>” pipeline and “<|” backwards-pipeline operators are exclusively for convenience.

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. A function accepts an input.


2. A function, when provided with an input, follows an algorithm in its body.
3. A function generates an output.
4. A function is either total (always outputs a value) or partial (might loop infinitely or throw an
exception).
5. A function is pure (does not have any externally-visible effect, other than generating an output).
6. A function has a domain (set of possible inputs) and a codomain (set of possible outputs).
7. A function has an arity (number of curried inputs).
Function Techniques
We have learned about the following function techniques:

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…

● Be tall / short / average


● Have dark / light eyes
● Have dark / light hair
● Have beards
● …etc

All at the same time.

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

All at the same time.

You might also like