#> Processing 3
#> Processing 2
#> Processing 1
#> [1] 9 4 1
A function operator is a function that takes one (or more) functions as input and returns a function as output.
Function operators are a special case of function factories, since they return functions.
They are often used to wrap an existing function to provide additional capability, similar to python’s decorators.
Two function operator examples are purrr:safely() and memoise::memoise(). These can be found in purr and memoise:
Capturing Errors: turns errors into data!
#> List of 2
#> $ result:List of 4
#> ..$ : num 1.39
#> ..$ : num 1.27
#> ..$ : num 2.17
#> ..$ : NULL
#> $ error :List of 4
#> ..$ : NULL
#> ..$ : NULL
#> ..$ : NULL
#> ..$ :List of 2
#> .. ..$ message: chr "invalid 'type' (character) of argument"
#> .. ..$ call : language .Primitive("sum")(..., na.rm = na.rm)
#> .. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
purrr function operatorspurrr comes with three other function operators in a similar vein:
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).
quietly(): turns output, messages, and warning side-effects into output, message, and warning components of the output.
auto_browser(): automatically executes browser() inside the function when there’s an error.
Caching computations: avoid repeated computations!
#> [1] 3.054764
#> user system elapsed
#> 0.02 0.00 1.02
#> [1] 3.644634
#> user system elapsed
#> 0.00 0.00 1.03
#> [1] 7.451108
#> user system elapsed
#> 0.00 0.00 1.01
#> [1] 7.451108
#> user system elapsed
#> 0.00 0.00 0.01
Be careful about memoising impure functions!
How does safely() work?
The source code looks like this:
#> function (.f, otherwise = NULL, quiet = TRUE)
#> {
#> .f <- as_mapper(.f)
#> force(otherwise)
#> check_bool(quiet)
#> function(...) capture_error(.f(...), otherwise, quiet)
#> }
#> <bytecode: 0x00000183867aacf0>
#> <environment: namespace:purrr>
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.)
#> function (code, otherwise = NULL, quiet = TRUE)
#> {
#> tryCatch(list(result = code, error = NULL), error = function(e) {
#> if (!quiet) {
#> message("Error: ", conditionMessage(e))
#> }
#> list(result = otherwise, error = e)
#> })
#> }
#> <bytecode: 0x0000018386817b60>
#> <environment: namespace:purrr>
Here we make a function operator that add a little delay in reading each page:
#> user system elapsed
#> 0 0 0
#> user system elapsed
#> 0.00 0.00 0.11
And another to add a dot after nth invocation:
#> ..........
Can now use both of these function operators to express our desired result: