---
title: "Functional programming: Sectioning function to a smaller domain"
author: "Søren Højsgaard"
output: 
  rmarkdown::html_vignette
    toc: true
    number_sections: true
vignette: >
  %\VignetteIndexEntry{section_fun: Section functions to a smaller domain}
  %\VignetteEngine{knitr::knitr}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(
  collapse = TRUE,
  comment = "#>"
)
options("digits"=3)
library(doBy)
library(microbenchmark)
#devtools::load_all()
```


## Sectioning a function domain with `section_fun()`

The `section_fun` utility in **doBy** creates a new function by **fixing some arguments** of an existing function. The result is a *section* of the original function, defined only on the remaining arguments.

For example, if you have:

$$
f(x,y) = x + y
$$

then fixing $x=10$ yields:

$$
f_x(y) = 10 + y
$$

In `R` terms, `section_fun` lets you programmatically create such specialized versions.

---

## How `section_fun` works

`section_fun()` offers three ways to fix arguments:

1. **Defaults (method = "def")** – Inserts the fixed values as defaults in the argument list.
2. **Substitution (method = "sub")** – Rewrites the function body with the fixed values.
3. **Environment (method = "env")** – Stores fixed values in an auxiliary environment.

Example:

```{r}
fun  <- function(a, b, c=4, d=9) {
    a + b + c + d
}
```

```{r}
fun_def <- section_fun(fun, list(b=7, d=10))
fun_def
fun_body <- section_fun(fun, list(b=7, d=10), method="sub")
fun_body
fun_env <- section_fun(fun, list(b=7, d=10), method = "env")
fun_env
```

You can inspect the environment-based section:

```{r}
get_section(fun_env) 
## same as: attr(fun_env, "arg_env")$args 
get_fun(fun_env) 
## same as: environment(fun_env)$fun
```




Example evaluations:

```{r}
fun(a=10, b=7, c=5, d=10)
fun_def(a=10, c=5)
fun_body(a=10, c=5)
fun_env(a=10, c=5)
```





## Benchmarking example

Suppose you want to benchmark a function for different input values without writing repetitive code:

```{r}
inv_toep <- function(n) {
    solve(toeplitz(1:n))
}
```

Instead of typing the following

```{r, eval=F}
microbenchmark(
    inv_toep(4), inv_toep(8), inv_toep(16),
    times=3
)
```

you can create specialized versions programmatically:

```{r}
n.vec  <- c(4, 8, 16)
fun_list <- lapply(n.vec,
                   function(ni) {
                       section_fun(inv_toep, list(n=ni))
                   })
fun_list
```

Inspect and evaluate:

```{r}
fun_list[[1]]
fun_list[[1]]()
```

To use with microbenchmark, we need expressions:

```{r}
bquote_list <- function(fun_list) {
    lapply(fun_list, function(g){
        bquote(.(g)())
    })
}
```

We get:

```{r}
bq_fun_list <- bquote_list(fun_list)
bq_fun_list
bq_fun_list[[1]]
eval(bq_fun_list[[1]])
```


Now run:

```{r}
microbenchmark(
  list = bq_fun_list,
  times = 5
)
```

Running the code below provides a benchmark of the different ways of sectioning in terms of speed.

```{r}
n.vec  <- seq(20, 80, by=20)
fun_def <- lapply(n.vec,
                  function(n){
                      section_fun(inv_toep, list(n=n), method="def")
                  })
fun_body <- lapply(n.vec,
                  function(n){
                      section_fun(inv_toep, list(n=n), method="sub")
                  })
fun_env <- lapply(n.vec,
                  function(n){
                      section_fun(inv_toep, list(n=n), method="env")
                  })

names(fun_def)  <- paste0("def", n.vec)
names(fun_body) <- paste0("body", n.vec)
names(fun_env)  <- paste0("env", n.vec)

bq_fun_list <- bquote_list(c(fun_def, fun_body, fun_env))
bq_fun_list |> head()

mb <- microbenchmark(
  list  = bq_fun_list,
  times = 2
)
mb
```

