frite is a package that enables us to easily write and modify functions. It can be used to inject, assign, or remove code in a function. There are also functions that enable you to use this package more easily. You can use the diagnostic functions to test any function that you can think of. It also has applications in code generation or metaprogramming.
tictocify
returns a nearly identical timed version of its input is.output.same
performs call on multiple functions and sees if they matchline_assign
inserts assign()
into a function line_insert
inserts code into a function line_remove
removes code from a function copy_args
copies the arguments of one function to anotherlist_body
converts function body to a list plot_body
plots list_body()
so you can inspect the body# Defining a new function
reduce_timed <- tictocify(reduce)
# Now to test it against the original reduce
reduce_timed(1:100000, sum, .init = 0)
#> 0.14 sec elapsed
#> [1] 5000050000
is.output.same(reduce_timed(1:100000, sum, .init = 0), reduce)
#> 0.14 sec elapsed
#> [1] TRUE
tictocify
will return a nearly identical timed version of a function. It does this by creating a function call and inserting tic()
, toc()
, and a return statement around the call.
is.output.same
evaluates the call in the first argument and replaces the function in the first call and re-evaluates it with a new output and checks that they are identical.
# Constructing a different version of reduce_timed
(reduce_timed1 <- line_insert(reduce, after_line = 1, quote(tic())))
#> function (.x, .f, ..., .init)
#> {
#> tic()
#> reduce_impl(.x, .f, ..., .init = .init, .left = TRUE)
#> }
#> <environment: namespace:purrr>
(reduce_timed1 <- line_assign(reduce_timed1, line = 3, 'value'))
#> function (.x, .f, ..., .init)
#> {
#> tic()
#> assign("value", reduce_impl(.x, .f, ..., .init = .init, .left = TRUE))
#> }
#> <environment: namespace:purrr>
(reduce_timed1 <- line_insert(reduce_timed1, after_line = 3, quote(toc())))
#> function (.x, .f, ..., .init)
#> {
#> tic()
#> assign("value", reduce_impl(.x, .f, ..., .init = .init, .left = TRUE))
#> toc()
#> }
#> <environment: namespace:purrr>
(reduce_timed1 <- line_insert(reduce_timed1, after_line = 4,
quote(return(value))))
#> function (.x, .f, ..., .init)
#> {
#> tic()
#> assign("value", reduce_impl(.x, .f, ..., .init = .init, .left = TRUE))
#> toc()
#> return(value)
#> }
#> <environment: namespace:purrr>
is.output.same(reduce_timed(1:100000, sum, .init = 0), reduce_timed1)
#> 0.17 sec elapsed
#> 0.13 sec elapsed
#> [1] TRUE
Note: Everything above can be piped.
These types of can modifications can be difficult to make sequentially, so there are a couple of helper functions to allow you to see what you’re doing.
plot_body(map)
#> function (.x, .f, ...)
#> {
#> .f <- as_mapper(.f, ...)
#> .Call(map_impl, environment(), ".x", ".f", "list")
#> }
#> <bytecode: 0x00000000184b2840>
#> <environment: namespace:purrr>
map_hello <- map %>%
line_insert(after_line = 1, quote(print("Hello!")))
list_body(map_hello)
#> [[1]]
#> `{`
#>
#> [[2]]
#> print("Hello!")
#>
#> [[3]]
#> .f <- as_mapper(.f, ...)
#>
#> [[4]]
#> .Call(map_impl, environment(), ".x", ".f", "list")
map_hello(list(1, 2, "b"), assertthat::is.number)
#> [1] "Hello!"
#> [[1]]
#> [1] TRUE
#>
#> [[2]]
#> [1] TRUE
#>
#> [[3]]
#> [1] FALSE
You can use the modification functions to build functions with recursion. A simple example is below.
spammer <- function() {}
add_print <- function(.f, n) {
.f <- line_insert(.f, 1, quote(print("a")))
n <- n - 1
if (n > 0) return(add_print(.f, n))
if (n == 0) return(.f)
}
add_print(spammer, 5)
#> function ()
#> {
#> print("a")
#> print("a")
#> print("a")
#> print("a")
#> print("a")
#> }
If you want to learn more, you can view the reference manual or install frite
and begin experimenting.