html.json (19975B)
1 { 2 "hash": "3855cbc6f04c2a735a53df74125f5988", 3 "result": { 4 "engine": "knitr", 5 "markdown": "---\nengine: knitr\ntitle: Function factories\n---\n\n## Learning objectives:\n\n- Understand what a function factory is\n- Recognise how function factories work\n- Learn about non-obvious combination of function features\n- Generate a family of functions from data\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(rlang)\nlibrary(ggplot2)\nlibrary(scales)\n```\n:::\n\n\n\n## What is a function factory?\n\n\nA **function factory** is a function that makes (returns) functions\n\nFactory made function are **manufactured functions**.\n\n\n::: {.cell layout-align=\"center\"}\n::: {.cell-output-display}\n{fig-align='center' fig-alt='https://epsis.com/no/operations-centers-focus-on-ways-of-working/' width=512}\n:::\n:::\n\n\n\n\n## How does a function factory work?\n\n::: {.cell layout-align=\"center\"}\n::: {.cell-output-display}\n{fig-align='center' width=130}\n:::\n:::\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\npower1 <- function(exp) {\n function(x) {\n x ^ exp\n }\n}\n\nsquare <- power1(2)\ncube <- power1(3)\n```\n:::\n\n`power1()` is the function factory and `square()` and `cube()` are manufactured functions.\n\n## Important to remember\n\n1. R has First-class functions (can be created with `function()` and `<-`)\n\n> R functions are objects in their own right, a language property often called “first-class functions” \n> -- [Section 6.2.3](https://adv-r.hadley.nz/functions.html?q=first%20class#first-class-functions)\n\n2. Functions capture (enclose) environment in which they are created\n\n\n::: {.cell}\n\n```{.r .cell-code}\nf <- function(x) function(y) x + y\nfn_env(f) # The function f()\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> <environment: R_GlobalEnv>\n```\n\n\n:::\n\n```{.r .cell-code}\nfn_env(f()) # The function created by f()\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> <environment: 0x0000029fbf09a6d0>\n```\n\n\n:::\n:::\n\n\n3. Functions create a new environment on each run\n\n::: {.cell}\n\n```{.r .cell-code}\nf <- function(x) {\n function() x + 1\n}\nff <- f(1)\nff()\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 2\n```\n\n\n:::\n\n```{.r .cell-code}\nff()\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 2\n```\n\n\n:::\n:::\n\n\n\n## Fundamentals - Environment\n\n- Environment when function is created defines arguments in the function\n- Use `env_print(fun)` and `fn_env()` to explore\n\n\n::: {.cell}\n\n```{.r .cell-code}\nenv_print(square)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> <environment: 0x0000029fc0169900>\n#> Parent: <environment: global>\n#> Bindings:\n#> • exp: <lazy>\n```\n\n\n:::\n\n```{.r .cell-code}\nfn_env(square)$exp\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 2\n```\n\n\n:::\n:::\n\n\n{width=50% fig-align=center}\n\n## Fundamentals - Forcing\n\n- Lazy evaluation means arguments only evaluated when used\n- \"[can] lead to a real head-scratcher of a bug\"\n\n\n::: {.cell}\n\n```{.r .cell-code}\nx <- 2\nsquare <- power1(x)\nx <- 3\nsquare(4)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 64\n```\n\n\n:::\n:::\n\n\n- *Only applies if passing object as argument*\n- Here argument `2` evaluated when function called\n\n\n::: {.cell}\n\n```{.r .cell-code}\nsquare <- power1(2)\nx <- 3\nsquare(4)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 16\n```\n\n\n:::\n:::\n\n\nSo use `force()`! (Unless you want it to change with the `x` in the parent environment)\n\n## Forcing - Reiterated\n\nOnly required if the argument is **not** evaluated before the new function is created:\n\n::: {.cell}\n\n```{.r .cell-code}\npower1 <- function(exp) {\n stopifnot(is.numeric(exp))\n function(x) x ^ exp\n}\n\nx <- 2\nsquare <- power1(x)\nx <- 3\nsquare(4)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 16\n```\n\n\n:::\n:::\n\n\n## Fundamentals - Stateful functions\n\nBecause\n\n- The enclosing environment is unique and constant, and\n- We have `<<-` (super assignment)\n\nWe can *change* that enclosing environment and keep track of that state\nacross iterations (!)\n\n- `<-` Assignment in *current* environment\n- `<<-` Assignment in *parent* environment\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnew_counter <- function() {\n i <- 0 \n function() {\n i <<- i + 1 # second assignment (super assignment)\n i\n }\n}\n\ncounter_one <- new_counter()\ncounter_two <- new_counter()\nc(counter_one(), counter_one(), counter_one())\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 1 2 3\n```\n\n\n:::\n\n```{.r .cell-code}\nc(counter_two(), counter_two(), counter_two())\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 1 2 3\n```\n\n\n:::\n:::\n\n\n\n> \"As soon as your function starts managing the state of multiple variables, it’s better to switch to R6\"\n\n## Fundamentals - Garbage collection\n\n- Because environment is attached to (enclosed by) function, temporary objects\ndon't go away.\n\n**Cleaning up** using `rm()` inside a function:\n\n::: {.cell}\n\n```{.r .cell-code}\nf_dirty <- function(n) {\n x <- runif(n)\n m <- mean(x)\n function() m\n}\n\nf_clean <- function(n) {\n x <- runif(n)\n m <- mean(x)\n rm(x) # <---- Important part!\n function() m\n}\n\nlobstr::obj_size(f_dirty(1e6))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> 8.00 MB\n```\n\n\n:::\n\n```{.r .cell-code}\nlobstr::obj_size(f_clean(1e6))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> 504 B\n```\n\n\n:::\n:::\n\n\n\n## Useful Examples - Histograms and binwidth\n\n**Useful when...** \n\n- You need to pass a function \n- You don't want to have to re-write the function every time \n (the *default* behaviour of the function should be flexible)\n\n\nFor example, these bins are not appropriate\n\n::: {.cell}\n\n```{.r .cell-code}\nsd <- c(1, 5, 15)\nn <- 100\ndf <- data.frame(x = rnorm(3 * n, sd = sd), sd = rep(sd, n))\n\nggplot(df, aes(x)) + \n geom_histogram(binwidth = 2) + \n facet_wrap(~ sd, scales = \"free_x\") + \n labs(x = NULL)\n```\n\n::: {.cell-output-display}\n{width=672}\n:::\n:::\n\n\nWe could just make a function...\n\n::: {.cell}\n\n```{.r .cell-code}\nbinwidth_bins <- function(x) (max(x) - min(x)) / 20\n\nggplot(df, aes(x = x)) + \n geom_histogram(binwidth = binwidth_bins) + \n facet_wrap(~ sd, scales = \"free_x\") + \n labs(x = NULL)\n```\n\n::: {.cell-output-display}\n{width=672}\n:::\n:::\n\n\nBut if we want to change the number of bins (20) we'd have to re-write the function\neach time.\n\nIf we use a factory, we don't have to do that.\n\n::: {.cell}\n\n```{.r .cell-code}\nbinwidth_bins <- function(n) {\n force(n)\n function(x) (max(x) - min(x)) / n\n}\n\nggplot(df, aes(x = x)) + \n geom_histogram(binwidth = binwidth_bins(20)) + \n facet_wrap(~ sd, scales = \"free_x\") + \n labs(x = NULL, title = \"20 bins\")\n```\n\n::: {.cell-output-display}\n{width=672}\n:::\n\n```{.r .cell-code}\nggplot(df, aes(x = x)) + \n geom_histogram(binwidth = binwidth_bins(5)) + \n facet_wrap(~ sd, scales = \"free_x\") + \n labs(x = NULL, title = \"5 bins\")\n```\n\n::: {.cell-output-display}\n{width=672}\n:::\n:::\n\n\n> Similar benefit in Box-cox example\n\n## Useful Examples - Wrapper\n\n**Useful when...**\n\n- You want to create a function that wraps a bunch of other functions\n\nFor example, `ggsave()` wraps a bunch of different graphics device functions: \n\n\n::: {.cell}\n\n```{.r .cell-code}\n# (Even more simplified)\nplot_dev <- function(ext, dpi = 96) {\n force(dpi)\n \n switch(\n ext,\n svg = function(filename, ...) svglite::svglite(file = filename, ...),\n png = function(...) grDevices::png(..., res = dpi, units = \"in\"),\n jpg = ,\n jpeg = function(...) grDevices::jpeg(..., res = dpi, units = \"in\"),\n stop(\"Unknown graphics extension: \", ext, call. = FALSE)\n )\n}\n```\n:::\n\n\nThen `ggsave()` uses\n\n```\nggsave <- function(...) {\n dev <- plot_dev(device, filename, dpi = dpi)\n ...\n dev(filename = filename, width = dim[1], height = dim[2], bg = bg, ...)\n ...\n}\n```\n\nOtherwise, would have to do something like like a bunch of if/else statements.\n\n## Useful Examples - Optimizing\n\n**Useful when...**\n\n- Want to pass function on to `optimise()`/`optimize()`\n- Want to perform pre-computations to speed things up\n- Want to re-use this for other datasets\n\n(*Skipping to final results from section*)\n\nHere, using MLE want to to find the most likely value of lambda for a Poisson distribution\nand this data.\n\n::: {.cell}\n\n```{.r .cell-code}\nx1 <- c(41, 30, 31, 38, 29, 24, 30, 29, 31, 38)\n```\n:::\n\n\nWe'll create a function that creates a lambda assessment function for a given \ndata set.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nll_poisson <- function(x) {\n n <- length(x)\n sum_x <- sum(x)\n c <- sum(lfactorial(x))\n\n function(lambda) {\n log(lambda) * sum_x - n * lambda - c\n }\n}\n```\n:::\n\n\nWe can use this on different data sets, but here use ours `x1`\n\n::: {.cell}\n\n```{.r .cell-code}\nll <- ll_poisson(x1)\nll(10) # Log-probility of a lambda = 10\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] -183.6405\n```\n\n\n:::\n:::\n\n\nUse `optimise()` rather than trial and error\n\n::: {.cell}\n\n```{.r .cell-code}\noptimise(ll, c(0, 100), maximum = TRUE)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> $maximum\n#> [1] 32.09999\n#> \n#> $objective\n#> [1] -30.26755\n```\n\n\n:::\n:::\n\n\nResult: Highest log-probability is -30.3, best lambda is 32.1\n\n\n## Function factories + functionals\n\nCombine functionals and function factories to turn data into many functions.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnames <- list(\n square = 2, \n cube = 3, \n root = 1/2, \n cuberoot = 1/3, \n reciprocal = -1\n)\nfuns <- purrr::map(names, power1)\nnames(funs)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] \"square\" \"cube\" \"root\" \"cuberoot\" \"reciprocal\"\n```\n\n\n:::\n\n```{.r .cell-code}\nfuns$root(64)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 8\n```\n\n\n:::\n\n```{.r .cell-code}\nfuns$square(3)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 9\n```\n\n\n:::\n:::\n\n\nAvoid the prefix with\n\n- `with()` - `with(funs, root(100))`\n - Temporary, clear, short-term\n- `attach()` - `attach(funs)` / `detach(funs)`\n - Added to search path (like package function), cannot be overwritten, but can be attached multiple times!\n- `rlang::env_bind` - `env_bind(globalenv(), !!!funs)` / `env_unbind(gloablenv(), names(funs))`\n - Added to global env (like created function), can be overwritten\n\n<!--\n## EXTRA - Previous set of slides\n\nGraphical factories **useful function factories**, such as:\n\n1. Labelling with:\n\n * formatter functions\n \n\n::: {.cell}\n\n```{.r .cell-code}\ny <- c(12345, 123456, 1234567)\ncomma_format()(y)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] \"12,345\" \"123,456\" \"1,234,567\"\n```\n\n\n:::\n:::\n\n\n::: {.cell}\n\n```{.r .cell-code}\nnumber_format(scale = 1e-3, suffix = \" K\")(y)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] \"12 K\" \"123 K\" \"1 235 K\"\n```\n\n\n:::\n:::\n\nThey are more commonly used inside a ggplot:\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\na_ggplot_object + \n scale_y_continuous(\n labels = comma_format()\n)\n```\n\n::: {.cell-output-display}\n{width=672}\n:::\n:::\n\n\n2. Using binwidth in facet histograms\n\n * binwidth_bins\n \n\n::: {.cell}\n\n```{.r .cell-code}\nbinwidth_bins <- function(n) {\n force(n)\n \n function(x) {\n (max(x) - min(x)) / n\n }\n}\n```\n:::\n\n \nOr use a concatenation of this typr of detecting number of bins functions:\n\n - nclass.Sturges()\n - nclass.scott()\n - nclass.FD()\n \n\n::: {.cell}\n\n```{.r .cell-code}\nbase_bins <- function(type) {\n fun <- switch(type,\n Sturges = nclass.Sturges,\n scott = nclass.scott,\n FD = nclass.FD,\n stop(\"Unknown type\", call. = FALSE)\n )\n \n function(x) {\n (max(x) - min(x)) / fun(x)\n }\n}\n```\n:::\n\n \n\n3. Internals:\n\n * ggplot2:::plot_dev()\n\n\n## Non-obvious combinations\n\n\n- The **Box-Cox** transformation.\n- **Bootstrap** resampling.\n- **Maximum likelihood** estimation.\n\n\n### Statistical factories\n\nThe **Box-Cox** transformation towards normality:\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox1 <- function(x, lambda) {\n stopifnot(length(lambda) == 1)\n \n if (lambda == 0) {\n log(x)\n } else {\n (x ^ lambda - 1) / lambda\n }\n}\n```\n:::\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nboxcox2 <- function(lambda) {\n if (lambda == 0) {\n function(x) log(x)\n } else {\n function(x) (x ^ lambda - 1) / lambda\n }\n}\n\nstat_boxcox <- function(lambda) {\n stat_function(aes(colour = lambda), fun = boxcox2(lambda), size = 1)\n}\n\nplot1 <- ggplot(data.frame(x = c(0, 5)), aes(x)) + \n lapply(c(0.5, 1, 1.5), stat_boxcox) + \n scale_colour_viridis_c(limits = c(0, 1.5))\n```\n\n::: {.cell-output .cell-output-stderr}\n\n```\n#> Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.\n#> ℹ Please use `linewidth` instead.\n```\n\n\n:::\n\n```{.r .cell-code}\n# visually, log() does seem to make sense as the transformation\n# for lambda = 0; as values get smaller and smaller, the function\n# gets close and closer to a log transformation\nplot2 <- ggplot(data.frame(x = c(0.01, 1)), aes(x)) + \n lapply(c(0.5, 0.25, 0.1, 0), stat_boxcox) + \n scale_colour_viridis_c(limits = c(0, 1.5))\nlibrary(patchwork)\nplot1+plot2\n```\n\n::: {.cell-output-display}\n{width=672}\n:::\n:::\n\n\n**Bootstrap generators**\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nboot_permute <- function(df, var) {\n n <- nrow(df)\n force(var)\n \n function() {\n col <- df[[var]]\n col[sample(n, replace = TRUE)]\n }\n}\n\nboot_mtcars1 <- boot_permute(mtcars, \"mpg\")\nhead(boot_mtcars1())\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 21.5 19.2 16.4 10.4 22.8 24.4\n```\n\n\n:::\n\n```{.r .cell-code}\n#> [1] 16.4 22.8 22.8 22.8 16.4 19.2\nhead(boot_mtcars1())\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 32.4 22.8 19.2 10.4 22.8 18.1\n```\n\n\n:::\n\n```{.r .cell-code}\n#> [1] 17.8 18.7 30.4 30.4 16.4 21.0\n```\n:::\n\n\n::: {.cell}\n\n```{.r .cell-code}\nboot_model <- function(df, formula) {\n mod <- lm(formula, data = df)\n fitted <- unname(fitted(mod))\n resid <- unname(resid(mod))\n rm(mod)\n\n function() {\n fitted + sample(resid)\n }\n} \n\nboot_mtcars2 <- boot_model(mtcars, mpg ~ wt)\nhead(boot_mtcars2())\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 24.48367 22.27620 26.63215 20.96952 25.77286 16.51064\n```\n\n\n:::\n\n```{.r .cell-code}\n#> [1] 25.0 24.0 21.7 19.2 24.9 16.0\nhead(boot_mtcars2())\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 29.70459 23.66597 25.75283 26.08372 25.77286 14.88789\n```\n\n\n:::\n\n```{.r .cell-code}\n#> [1] 27.4 21.0 20.3 19.4 16.3 21.3\n```\n:::\n\n\n**Maximum likelihood estimation**\n\n$$P(\\lambda,x)=\\prod_{i=1}^{n}\\frac{\\lambda^{x_i}e^{-\\lambda}}{x_i!}$$\n\n::: {.cell}\n\n```{.r .cell-code}\nlprob_poisson <- function(lambda, x) {\n n <- length(x)\n (log(lambda) * sum(x)) - (n * lambda) - sum(lfactorial(x))\n}\n```\n:::\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nx1 <- c(41, 30, 31, 38, 29, 24, 30, 29, 31, 38)\n```\n:::\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlprob_poisson(10, x1)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] -183.6405\n```\n\n\n:::\n\n```{.r .cell-code}\n#> [1] -184\nlprob_poisson(20, x1)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] -61.14028\n```\n\n\n:::\n\n```{.r .cell-code}\n#> [1] -61.1\nlprob_poisson(30, x1)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] -30.98598\n```\n\n\n:::\n\n```{.r .cell-code}\n#> [1] -31\n```\n:::\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nll_poisson1 <- function(x) {\n n <- length(x)\n\n function(lambda) {\n log(lambda) * sum(x) - n * lambda - sum(lfactorial(x))\n }\n}\n```\n:::\n\n\n::: {.cell}\n\n```{.r .cell-code}\nll_poisson2 <- function(x) {\n n <- length(x)\n sum_x <- sum(x)\n c <- sum(lfactorial(x))\n\n function(lambda) {\n log(lambda) * sum_x - n * lambda - c\n }\n}\n```\n:::\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nll1 <- ll_poisson2(x1)\n\nll1(10)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] -183.6405\n```\n\n\n:::\n\n```{.r .cell-code}\n#> [1] -184\nll1(20)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] -61.14028\n```\n\n\n:::\n\n```{.r .cell-code}\n#> [1] -61.1\nll1(30)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] -30.98598\n```\n\n\n:::\n\n```{.r .cell-code}\n#> [1] -31\n```\n:::\n\n\n::: {.cell}\n\n```{.r .cell-code}\noptimise(ll1, c(0, 100), maximum = TRUE)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> $maximum\n#> [1] 32.09999\n#> \n#> $objective\n#> [1] -30.26755\n```\n\n\n:::\n\n```{.r .cell-code}\n#> $maximum\n#> [1] 32.1\n#> \n#> $objective\n#> [1] -30.3\n```\n:::\n\n\n::: {.cell}\n\n```{.r .cell-code}\noptimise(lprob_poisson, c(0, 100), x = x1, maximum = TRUE)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> $maximum\n#> [1] 32.09999\n#> \n#> $objective\n#> [1] -30.26755\n```\n\n\n:::\n\n```{.r .cell-code}\n#> $maximum\n#> [1] 32.1\n#> \n#> $objective\n#> [1] -30.3\n```\n:::\n\n\n## Function factory applications\n\n\nCombine functionals and function factories to turn data into many functions.\n\n### Function factories + functionals\n\n::: {.cell}\n\n```{.r .cell-code}\nnames <- list(\n square = 2, \n cube = 3, \n root = 1/2, \n cuberoot = 1/3, \n reciprocal = -1\n)\nfuns <- purrr::map(names, power1)\n\nfuns$root(64)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 8\n```\n\n\n:::\n\n```{.r .cell-code}\n#> [1] 8\nfuns$root\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> function (x) \n#> x^exp\n#> <bytecode: 0x0000029fbed27b60>\n#> <environment: 0x0000029fbf4bb9a0>\n```\n\n\n:::\n\n```{.r .cell-code}\n#> function(x) {\n#> x ^ exp\n#> }\n#> <bytecode: 0x7fe85512a410>\n#> <environment: 0x7fe85b21f190>\n```\n:::\n\n\n::: {.cell}\n\n```{.r .cell-code}\nwith(funs, root(100))\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 10\n```\n\n\n:::\n\n```{.r .cell-code}\n#> [1] 10\n```\n:::\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nattach(funs)\n```\n\n::: {.cell-output .cell-output-stderr}\n\n```\n#> The following objects are masked _by_ .GlobalEnv:\n#> \n#> cube, square\n```\n\n\n:::\n\n```{.r .cell-code}\n#> The following objects are masked _by_ .GlobalEnv:\n#> \n#> cube, square\nroot(100)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 10\n```\n\n\n:::\n\n```{.r .cell-code}\n#> [1] 10\ndetach(funs)\n```\n:::\n\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrlang::env_bind(globalenv(), !!!funs)\nroot(100)\n```\n\n::: {.cell-output .cell-output-stdout}\n\n```\n#> [1] 10\n```\n\n\n:::\n\n```{.r .cell-code}\n#> [1] 10\n```\n:::\n\n\n\n::: {.cell}\n\n```{.r .cell-code}\nrlang::env_unbind(globalenv(), names(funs))\n```\n:::\n\n\n\n-->\n", 6 "supporting": [ 7 "10_files" 8 ], 9 "filters": [ 10 "rmarkdown/pagebreak.lua" 11 ], 12 "includes": {}, 13 "engineDependencies": {}, 14 "preserve": {}, 15 "postProcess": true 16 } 17 }