Macros

Macros are function-like preprocessor directives that allow you to define reusable code templates with parameters. Unlike simple #> define replacements, macros can accept arguments and expand into multiple lines of code.

Advantages

Basic Syntax

Macros are defined using #> macro on its own line, followed by a function definition. The macro ends with #> endmacro.

Syntax:

#> macro
MACRO_NAME <- function(arg1, arg2, ...) {
  body using .arg1, .arg2, etc.
}
#> endmacro

Global vs Local Macros

By default, macros are global - their expanded code is inserted directly without wrapping.

Use #> macro local to create a local macro that is wrapped in local({...}). This prevents the macro from polluting the calling environment, avoiding issues like accidentally overwriting variables.

Global Macro (Default)

#> macro
SETUP_ENV <- function(name) {
  .name_env <- new.env()
  .name_data <- list()
}
#> endmacro

SETUP_ENV(app)

Expands to:

app_env <- new.env()
app_data <- list()

Local Macro

#> macro local
._LOG_INFO <- function(msg) {
  cat("[INFO] ", msg, "\n", sep = "")
}
#> endmacro

._LOG_INFO("Application started")

Expands to:

local({
  cat("[INFO] ", "Application started", "\n", sep = "")
})

When to Use Each

Type Use When
#> macro (global) Default choice. Use when you need to create/modify variables in calling scope, or define functions.
#> macro local Safer, no side effects on calling scope. Use for self-contained operations.

Argument Syntax

Macro arguments use explicit markers for replacement:

Syntax Description Example (name = count)
name Not replaced (literal) name stays name
.name Value replacement .name becomes count
..name Stringification ..name becomes "count"

Note: Macro argument names cannot start with ..

Basic Example

#> macro
LOG_EVAL <- function(expr) {
  cat("Evaluating stuff\n")
  .expr
}
#> endmacro

LOG_EVAL(sum(1, 1, 1))

Expands to:

cat("Evaluating stuff\n")
sum(1, 1, 1)

Stringification

Use ..arg to get the argument as a string literal:

#> macro
DEBUG <- function(var) {
  cat(..var, "=", .var, "\n")
}
#> endmacro

my_value <- 42
DEBUG(my_value)

Expands to:

my_value <- 42
cat("my_value", "=", my_value, "\n")

Building Identifiers (Token Pasting)

Use .arg within identifier names to build new identifiers:

#> macro
GETTER <- function(name) {
  get_.name <- function() private$.name
}
#> endmacro

GETTER(count)

Expands to:

get_count <- function() private$count

Examples

Cleanup

Ensure you close your database connections when you’re done.

#> macro
CONNECT <- function() {
  con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:")
  on.exit(DBI::dbDisconnect(con))
}
#> endmacro

CONNECT()

Expands to:

con <- DBI::dbConnect(RSQLite::SQLite(), ":memory:")
on.exit(DBI::dbDisconnect(con))

Repeat

Macro to easily repeat a block of code.

#> macro
REPEAT <- function(n, action) {
  for (i in 1:.n) {
    .action
  }
}
#> endmacro

REPEAT(3, print("Hello"))

Expands to:

for (i in 1:3) {
  print("Hello")
}

Macros with Multiple Arguments

Macros can accept multiple arguments, which are separated by commas in both the definition and invocation.

#> macro
VALIDATE <- function(value, min, max) {
  if (.value < .min || .value > .max) {
    stop("Value out of range: ", .value)
  }
}
#> endmacro

x <- 15
VALIDATE(x, 0, 100)

Expands to:

x <- 15
if (x < 0 || x > 100) {
  stop("Value out of range: ", x)
}

Logging Macro

#> macro
LOG <- function(level, msg) {
  cat("[", .level, "] ", .msg, "\n", sep = "")
}
#> endmacro

LOG("INFO", "Application started")
LOG("ERROR", "Something went wrong")

Expands to:

cat("[", "INFO", "] ", "Application started", "\n", sep = "")
cat("[", "ERROR", "] ", "Something went wrong", "\n", sep = "")

Try-Catch Wrapper

#> macro
TRY_CATCH <- function(code, error_msg) {
  tryCatch({
    .code
  }, error = function(e) {
    cat(.error_msg, ":", e$message, "\n")
  })
}
#> endmacro

TRY_CATCH(risky_operation(), "Operation failed")

Expands to:

tryCatch({
  risky_operation()
}, error = function(e) {
  cat("Operation failed", ":", e$message, "\n")
})

Timing Macro

#> macro
TIME_IT <- function(expr) {
  start <- Sys.time()
  result <- .expr
  end <- Sys.time()
  cat("Execution time:", end - start, "\n")
  result
}
#> endmacro

TIME_IT(slow_computation())

Expands to:

start <- Sys.time()
result <- slow_computation()
end <- Sys.time()
cat("Execution time:", end - start, "\n")
result

Important Notes