bookclub-advr

DSLC Advanced R Book Club
git clone https://git.eamoncaddigan.net/bookclub-advr.git
Log | Files | Refs | README | LICENSE

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?