Introducing 'cryogenic' - a package to freeze function calls to modify and evaluate later

cryogenic

Lifecycle: experimental R build status

cryogenic is a package for freezing a function call, modifying its arguments and then evaluating later.

This package is written for a particular use case I have in mind, and may not be generally applicable. The functions are all in base R and quite small, and the reasoning for having this in a package is consistency of the interface.

See rlang for a much more comprehensive and generalisable approach.

My Use Case

  • Both the calling environment and evaluation environment are under my control.
    • I am not executing captured calls based on user input.
  • Need an R package for this as I need to use this code across multiple packages
  • Need eager evaluation of arguments when call is captured
  • Need to be able to update any arguments
    • Need to be able to update an argument to have a value of NULL
  • Need to be able to set arguments to a default value if they aren’t already present
  • Nearly everything is in lists
  • Need to be able to consistently add arbitrary meta information as an attribute on the call
  • There are probably other constraints which I’ll add as I think of them

What’s in the box

  • capture_call() to capture a call without evaluating it
  • modify_call() to modify call arguments
  • evaluate_call() to evaluate a call - and optionally modify the call arguments just prior to the evaluation.

Installation

You can install from GitHub with:

# install.package('remotes')
remotes::install_github('coolbutuseless/cryogenic')

Simple example 1

  • capture a call
  • modify the call
  • evaluate the call
library(cryogenic)

cc <- capture_call(runif(n = 10, min=-3))
cc
runif(n = 10, min = -3)
cc <- modify_call(cc, defaults = list(min=0, max=5), update = list(n = 5))
cc
runif(n = 5, min = -3, max = 5)
evaluate_call(cc)
[1] -0.8759307 -0.0230088  1.5828269  4.2656623 -1.3865446

Simple example 2

  • capture a call
  • simultaneously update a call and evalute it
library(cryogenic)

cc <- capture_call(runif(n = 10, min=-3))
cc
runif(n = 10, min = -3)
evaluate_call(cc, defaults = list(min=0, max=5), update = list(n = 15))
 [1]  4.18711748  4.55740215  2.28638234  2.03291235 -2.50570984 -1.35220340
 [7] -1.58754598  2.49618277  0.07282975  3.15873136  0.98159394  2.74094807
[13]  4.93524876  0.04028144  3.21956177

Simple example 3

library(cryogenic)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Capture a call as-is.
#
# * the function does not have to exist in the capturing environment
# * By default, all arguments are eagerly evaluated in the environment in which
#   the call was captured
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc <- capture_call(farnarkle(6, x = 2, y = 3, z = 2+9, error = TRUE))
cc
farnarkle(6, x = 2, y = 3, z = 11, error = TRUE)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Update the arguments to the call
#
# * 'defaults' are only added to the arguments if the named argument does 
#    not already exist there.
# * 'update' values are added to the arguments unconditionally
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
cc <- modify_call(
  cc, 
  defaults = list(x = NULL, y = 7), 
  update   = list(z = NULL, na.rm = TRUE),
  delete   = c('error')
)

cc
farnarkle(6, x = 2, y = 3, z = NULL, na.rm = TRUE)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 'farnarkle' is not yet defined, so evaluation should fail
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
evaluate_call(cc)
Error in farnarkle(6, x = 2, y = 3, z = NULL, na.rm = TRUE): could not find function "farnarkle"
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# define farnarkle and it should work
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
farnarkle <- function(...) {print("Hello #Rstats")}
evaluate_call(cc)
[1] "Hello #Rstats"

Acknowledgements

  • R Core for developing and maintaining the language.
  • CRAN maintainers, for patiently shepherding packages onto CRAN and maintaining the repository