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
- Easy metaprogramming: Write code that generates code, enabling powerful abstractions and code generation patterns.
- Zero overhead: Macros have no runtime performance cost since they’re expanded at compile time.
- Compile time expansion: All macro processing happens during preprocessing, before your code executes.
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.
}
#> endmacroGlobal vs Local Macros
By default, macros are global - expanded
code is inserted directly. Use #> macro local
to wrap expansion in local({...}), preventing
side effects on the calling environment.
#> macro
SETUP_ENV <- function(name) {
.name_env <- new.env()
}
#> endmacro
SETUP_ENV(app) # Expands to: app_env <- new.env()#> macro local
LOG_INFO <- function(msg) {
cat("[INFO] ", .msg, "\n", sep = "")
}
#> endmacro
LOG_INFO("Started") # Expands to: local({ cat("[INFO] ", "Started", "\n", sep = "") })| Type | Use When |
|---|---|
#> macro (global) |
Need to create/modify variables in calling scope |
#> macro local |
Self-contained operations, no side effects |
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 ..
Example
This macro demonstrates value replacement
(.var), stringification (..var), and
token pasting (.name within identifiers):
#> macro
ACCESSOR <- function(name) {
get_.name <- function() {
cat("Accessing:", ..name, "\n")
private$.name
}
}
#> endmacro
ACCESSOR(count)Expands to:
get_count <- function() {
cat("Accessing:", "count", "\n")
private$count
}Namespaced Macros
When importing macros from a package using
#> import pkg::file.rh, macros are prefixed
with the package namespace to avoid naming conflicts.
#> import logger::macros.rhIf macros.rh (in the logger
package’s inst/ directory) defines:
#> macro
LOG <- function(msg) {
cat("[LOG] ", .msg, "\n", sep = "")
}
#> endmacroYou call it with the namespace prefix:
logger::LOG("Application started")