11.Rmd (4247B)
1 --- 2 engine: knitr 3 title: Function operators 4 --- 5 6 ## Learning objectives: 7 8 - Define function operator 9 - Explore some existing function operators 10 - Make our own function operator 11 12 13 14 ## Introduction 15 16 <!--- ```{r 10-01, fig.align='center',fig.dim="50%", fig.cap="Credits: 2001-2003 Michael P.Frank (https://slideplayer.com/slide/17666226/)",echo=FALSE} 17 18 knitr::include_graphics("images/11-maths_example.png") 19 ``` ---> 20 21 - A **function operator** is a function that takes one (or more) functions as input and returns a function as output. 22 23 - Function operators are a special case of **function factories**, since they return functions. 24 25 - They are often used to wrap an existing function to provide additional capability, similar to python's **decorators**. 26 27 ```{r 11-01_0} 28 chatty <- function(f) { 29 force(f) 30 31 function(x, ...) { 32 res <- f(x, ...) 33 cat("Processing ", x, "\n", sep = "") 34 res 35 } 36 } 37 38 f <- function(x) x ^ 2 39 s <- c(3, 2, 1) 40 41 purrr::map_dbl(s, chatty(f)) 42 43 ``` 44 45 ## Existing function operators 46 47 Two function operator examples are `purrr:safely()` and `memoise::memoise()`. These can be found in `purr` and `memoise`: 48 49 ```{r 11-01_1} 50 library(purrr) 51 library(memoise) 52 ``` 53 54 ## purrr::safely {-} 55 56 Capturing Errors: turns errors into data! 57 58 59 ```{r 11-02_0} 60 x <- list( 61 c(0.512, 0.165, 0.717), 62 c(0.064, 0.781, 0.427), 63 c(0.890, 0.785, 0.495), 64 "oops" 65 ) 66 ``` 67 68 ```{r 11-02_0b, eval=FALSE} 69 map_dbl(x, sum) 70 #> Error in .Primitive("sum")(..., na.rm = na.rm): invalid 'type' (character) of 71 #> argument 72 ``` 73 74 75 ```{r 11-02_0c} 76 # note use of map (not map_dbl), safely returns a lisst 77 78 out <- map(x, safely(sum)) 79 str(transpose(out)) 80 ``` 81 82 83 84 ## Other `purrr` function operators {-} 85 86 <!--- 87 ```{r 11-02, echo=FALSE,fig.align='center', fig.cap="Credits: www.nextptr.com"} 88 knitr::include_graphics("images/11-function_operators.png") 89 ``` 90 ---> 91 92 > purrr comes with three other function operators in a similar vein: 93 94 95 possibly(): returns a default value when there’s an error. It provides no way to tell if an error occured or not, so it’s best reserved for cases when there’s some obvious sentinel value (like NA). 96 97 quietly(): turns output, messages, and warning side-effects into output, message, and warning components of the output. 98 99 auto_browser(): automatically executes browser() inside the function when there’s an error. 100 101 102 ## memoise::memoise {-} 103 104 Caching computations: avoid repeated computations! 105 106 107 ```{r 11-02_01} 108 slow_function <- function(x) { 109 Sys.sleep(1) 110 x * 10 * runif(1) 111 } 112 system.time(print(slow_function(1))) 113 system.time(print(slow_function(1))) 114 ``` 115 116 ```{r 11-02_02} 117 fast_function <- memoise::memoise(slow_function) 118 system.time(print(fast_function(1))) 119 system.time(print(fast_function(1))) 120 ``` 121 122 > Be careful about memoising impure functions! 123 124 ## Exercise {-} 125 126 How does `safely()` work? 127 The source code looks like this: 128 129 ```{r 11-02_exp1} 130 safely 131 ``` 132 133 The real work is done in `capture_error` which is defined in the package **namespace**. We can access it with the `:::` operator. (Could also grab it from the function's environment.) 134 135 ```{r 11-02_exp2} 136 purrr:::capture_error 137 ``` 138 139 ## Case study: make your own function operator 140 141 142 ```{r 11-03_01,eval=FALSE} 143 urls <- c( 144 "adv-r" = "https://adv-r.hadley.nz", 145 "r4ds" = "http://r4ds.had.co.nz/" 146 # and many many more 147 ) 148 path <- paste(tempdir(), names(urls), ".html") 149 150 walk2(urls, path, download.file, quiet = TRUE) 151 ``` 152 153 154 Here we make a function operator that add a little delay in reading each page: 155 156 ```{r 11-03_02} 157 delay_by <- function(f, amount) { 158 force(f) 159 force(amount) 160 161 function(...) { 162 Sys.sleep(amount) 163 f(...) 164 } 165 } 166 system.time(runif(100)) 167 168 system.time(delay_by(runif, 0.1)(100)) 169 170 ``` 171 172 173 And another to add a dot after nth invocation: 174 175 ```{r 11_03-04} 176 dot_every <- function(f, n) { 177 force(f) 178 force(n) 179 180 i <- 0 181 function(...) { 182 i <<- i + 1 183 if (i %% n == 0) cat(".") 184 f(...) 185 } 186 } 187 188 walk(1:100, dot_every(runif, 10)) 189 190 ``` 191 192 Can now use both of these function operators to express our desired result: 193 194 ```{r 11-03_05, eval=FALSE} 195 walk2( 196 urls, path, 197 download.file %>% dot_every(10) %>% delay_by(0.1), 198 quiet = TRUE 199 ) 200 ``` 201 202 ## Exercise {-} 203 204 2) Should you memoise file.download? Why or why not?