diff --git a/03-functional-programming.qmd b/03-functional-programming.qmd
new file mode 100644
index 0000000..61a8e6e
--- /dev/null
+++ b/03-functional-programming.qmd
@@ -0,0 +1,278 @@
+# A primer on functional programming
+
+
+```{r, echo = F}
+knitr::include_graphics("img/lambda.png")
+```
+
+
+What you'll have learned by the end of the chapter: writing your own functions,
+functional programming basics (map, reduce, anonymous functions and higher-order
+functions).
+
+## Introduction
+
+Functional programming is a way of writing programs that relies exclusively on
+the evalutation of functions. Mathematical functions have a very neat property:
+for any given input, they ALWAYS return exactly the same output. This is what we
+want to achieve with the functions that we will write. Functions that always
+return the same result are called pure, and a language that only allows writing
+pure functions is called a pure functional programming language. R is not a pure
+functional programming language, so we have to be careful not to write impure
+functions that manipulate the global state.
+
+But what is state? Run the following code in your console:
+
+```{r, eval = F}
+ls()
+```
+
+This will list every object defined in the global environment. Now run the
+following line:
+
+```{r, eval = F}
+
+x <- 1
+
+```
+
+and then `ls()` again. `x` should now be listed alongside the other objects. You
+just manipulated the state of your current R session. Now if you run something
+like:
+
+```{r, eval = F}
+
+x + 1
+
+```
+
+This will produce `2`. We want to avoid pipelines that depend on some definition
+of some global variable somewhere, which could be subject to change, because
+this could mean that 2 different runs of the same pipeline could produce 2
+different results. Notice that I used the verb *avoid* in the sentence before.
+This is sometimes not possible to avoid. Such situations have to be carefully
+documented and controlled.
+
+As a more realistic example, imagine that within the pipeline you set up, some
+random numbers are generated. For example, to generate 10 random draws from a
+normal distribution:
+
+```{r}
+rnorm(n = 10)
+```
+
+Each time you run this line, you will get another set of 10 random numbers. This
+is obviously a good thing in interactive data analysis, but much less so when
+running a pipeline programmatically. R provides a way to fix the random seed,
+which will make sure you always get the same random numbers:
+
+```{r}
+set.seed(1234)
+rnorm(n = 10)
+```
+
+But `set.seed()` only works for one call, so you must call it again if you need
+the random numbers again:
+
+```{r}
+set.seed(1234)
+rnorm(10)
+
+rnorm(10)
+
+set.seed(1234)
+rnorm(10)
+```
+
+The problem with `set.seed()` is that you only partially solve the problem of
+`rnorm()` not being pure; this is because while `rnorm()` now does return the
+same output for the same input, this only works if you manipulate the state of
+your program to change the seed beforehand. Ideally, we would like to have a
+pure version of `rnorm()`, which would be self-contained and not depend on the
+value of the seed defined in the global environment. There is a package
+developped by Posit (the makers of RStudio and the packages from the
+*tidyverse*), called `{withr}` which allows to rewrite our functions in a pure
+way. `{withr}` has several functions, all starting with `with_` that allow users
+to run code with some temporary defined variables, without altering the global
+environment. For example, it is possible to run a `rnorm()` with a seed, using
+`withr::with_seed()`:
+
+```{r}
+library(withr)
+
+with_seed(seed = 1234, {
+ rnorm(10)
+})
+```
+
+But ideally you’d want to go a step further and define a new function
+that is pure. To turn an impure function into a pure function, you usually only
+need to add some arguments to it. This is how we would create a `pure_rnorm()`
+function:
+
+```{r}
+pure_rnorm <- function(..., seed){
+
+ with_seed(seed, rnorm(...))
+}
+
+pure_rnorm(10, seed = 1234)
+```
+
+`pure_rnorm()` is now self-contained, and does not pollute the global
+environment. We’re going to learn how to write functions in just a
+bit, so don’t worry if the code above does not make sense yet.
+
+
+```{r, echo = F}
+knitr::include_graphics("img/cat_loops.png")
+```
+
+
+A very practical consequence of using functional programming is that loops are
+not used, because loops are imperative and imperative programming is all about
+manipulating state. However, there are situations where loops are more efficient
+than the alternative (in R at least). So we will still learn and use them, but
+only when absolutely necessary, and we will always encapsulate a loop inside a
+function. Just like with the example above, this ensures that we have a pure,
+self-contained function that we can reason about easily. What I mean by this, is
+that loops are not always very easy to decipher. The concept of loops is simple
+enough: take this instruction, and repeat it N times. But in practice, if
+you’re reading code, it is not possible to understand what a loop is
+doing at first glance. There are only two solutions in this case:
+
+- you’re lucky and there are comments that explain what the loop is doing;
+- you have to let the loop run either in your head or in a console with some examples to really understand whit is going on.
+
+For example, consider the following code:
+
+```{r}
+suppressPackageStartupMessages(library(dplyr))
+
+data(starwars)
+
+sum_humans <- 0
+sum_others <- 0
+n_humans <- 0
+n_others <- 0
+
+for(i in seq_along(1:nrow(starwars))){
+
+ if(!is.na(unlist(starwars[i, "species"])) &
+ unlist(starwars[i, "species"]) == "Human"){
+ if(!is.na(unlist(starwars[i, "height"]))){
+ sum_humans <- sum_humans + unlist(starwars[i, "height"])
+ n_humans <- n_humans + 1
+ } else {
+
+ 0
+
+ }
+
+ } else {
+ if(!is.na(unlist(starwars[i, "height"]))){
+ sum_others <- sum_others + unlist(starwars[i, "height"])
+ n_others <- n_others + 1
+ } else {
+ 0
+ }
+ }
+}
+
+mean_height_humans <- sum_humans/n_humans
+mean_height_others <- sum_others/n_others
+```
+
+What this does is not immediately obvious. The only hint you get are the two
+last lines, where you can read that we compute the average height for humans and
+non-humans in the sample. And this code could look a lot worse, because I am
+using functions like `is.na()` to test if a value is `NA` or not, and
+I’m using `unlist()` as well. If you compare this mess to a
+functional approach, I hope that I can stop my diatribe against imperative style
+programming here:
+
+```{r}
+starwars %>%
+ group_by(is_human = species == "Human") %>%
+ summarise(mean_height = mean(height, na.rm = TRUE))
+```
+
+Not only is this shorter, it doesn’t even need any comments to
+explain what’s going on. If you’re using functions with
+explicit names, the code becomes self-explanatory.
+
+The other advantage of a functional (also called declarative) programming style
+is that you get function composition for free. Function composition is an
+operation that takes two functions *g* and *f* and returns a new function *h*
+such that $h(x) = g(f(x))$. Formally:
+
+```
+h = g ∘ f such that h(x) = g(f(x))
+```
+
+`∘` is the composition operator. You can read `g ∘ f` as
+*g after f*. When using functional programming, you can compose functions very
+easily, simply by using `|>` or `%>%`:
+
+```{r, eval = F}
+h <- f |> g
+```
+
+`f |> g` can be read as *f then g*, which is equivalent to *g after f*. Function
+composition might not seem like a big deal, but it actually is. If we structure
+our programs in this way, as a sequence of function calls, we get many benefits.
+Functions are easy to test, document, maintain, share and can be composed. This
+allows us to very succintly express complex workflows:
+
+```{r}
+starwars %>%
+ filter(skin_color == "light") %>%
+ select(species, sex, mass) %>%
+ group_by(sex, species) %>%
+ summarise(
+ total_individuals = n(),
+ min_mass = min(mass, na.rm = TRUE),
+ mean_mass = mean(mass, na.rm = TRUE),
+ sd_mass = sd(mass, na.rm = TRUE),
+ max_mass = max(mass, na.rm = TRUE),
+ .groups = "drop"
+ ) %>%
+ select(-species) %>%
+ tidyr::pivot_longer(-sex, names_to = "statistic", values_to = "value")
+```
+
+Needless to say, writing this in an imperative approach would be quite
+complicated.
+
+Another consequence of using functional programming is that our code will live
+in plain text files, and not in Jupyter (or equivalent) notebooks. Not only does
+imperative code have state, but notebooks themselves have a (hidden) state. You
+should avoid notebooks at all costs, even for experimenting.
+
+## Defining your own functions
+
+Let's first learn about actually writing functions. Read [chapter
+7](https://b-rodrigues.github.io/modern_R/defining-your-own-functions.html) of
+my other book.
+
+The most important concepts for this course are discussed in the following
+sections:
+
+- functions that take functions as arguments [(section 7.4)](https://b-rodrigues.github.io/modern_R/defining-your-own-functions.html#functions-that-take-functions-as-arguments-writing-your-own-higher-order-functions)
+- functions that take data (and the data's columns) as arguments [(section 7.6)](https://b-rodrigues.github.io/modern_R/defining-your-own-functions.html#functions-that-take-columns-of-data-as-arguments);
+
+## Functional programming
+
+You should ideally work through the whole of chapter 7, and then tackle [chapter
+8](https://b-rodrigues.github.io/modern_R/functional-programming.html). What's
+important there are:
+
+- `purrr::map()`, `purrr::reduce()` (sections [8.3.1](https://b-rodrigues.github.io/modern_R/functional-programming.html#doing-away-with-loops-the-map-family-of-functions) and [8.3.2](https://b-rodrigues.github.io/modern_R/functional-programming.html#reducing-with-purrr))
+- And list based workflows (section [8.4](https://b-rodrigues.github.io/modern_R/functional-programming.html#functional-programming-and-plotting))
+
+## Further reading
+
+- [Cleaner R Code with Functional Programming](https://towardsdatascience.com/cleaner-r-code-with-functional-programming-adc37931ef7a)
+- [Functional Programming (Chapter from Advanced R)](http://adv-r.had.co.nz/Functional-programming.html)
+- [Why you should(n't) care about Monads if you're an R programmer](https://www.brodrigues.co/blog/2022-04-11-monads/)
+- [Some learnings from functional programming you can use to write safer programs](https://www.brodrigues.co/blog/2022-05-26-safer_programs/)
diff --git a/_quarto.yml b/_quarto.yml
index 0050a03..ad735fb 100644
--- a/_quarto.yml
+++ b/_quarto.yml
@@ -14,6 +14,7 @@ book:
chapters:
- index.qmd
- 02-intro_R.qmd
+ - 03-functional-programming.qmd
page-navigation: true
bibliography: references.bib