Getting started with frite

By Christopher Peralta

Last updated: 2018-06-28

Introduction

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.

The functions

Diagnostic functions

# 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

Recursively building functions

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.