# Metaprogramming

> >

- **Type:** Skill
- **Install:** `agentstack add skill-jsperger-llm-r-skills-metaprogramming`
- **Verified:** Pending review
- **Seller:** [jsperger](https://agentstack.voostack.com/s/jsperger)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [jsperger](https://github.com/jsperger)
- **Source:** https://github.com/jsperger/llm-r-skills/tree/main/skills/metaprogramming

## Install

```sh
agentstack add skill-jsperger-llm-r-skills-metaprogramming
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# R Metaprogramming with rlang

Metaprogramming is the ability to defuse, create, and inject R expressions. The core pattern is **defuse-and-inject**: capture code as data, optionally transform it, then inject it into another context for evaluation.

## Quick Reference

| Task | Function/Operator |
|------|-------------------|
| Defuse your own expression | `expr(x + 1)` |
| Defuse user's single argument | `enquo(arg)` |
| Defuse user's `...` arguments | `enquos(...)` |
| Inject single expression | `!!` or `{{` |
| Splice list of expressions | `!!!` |
| Get expression from quosure | `quo_get_expr(q)` |
| Get environment from quosure | `quo_get_env(q)` |
| Build symbol from string | `sym("name")` |
| Build symbol with .data pronoun | `data_sym("name")` |
| Build symbols from vector | `syms(names)` / `data_syms(names)` |
| Auto-label expression | `as_label(quo)` |
| Format argument as string | `englue("{{ x }}")` |
| Interpolate name in dynamic dots | `"{name}" := value` |
| Interpolate argument in name | `"{{ arg }}" := value` |

## Defusing Expressions

Defusing stops evaluation and returns the expression as a tree-like object (a "blueprint" for computation).

```r
# Normal evaluation returns result
1 + 1
#> [1] 2

# Defusing returns the expression
expr(1 + 1)
#> 1 + 1
```

### expr() vs enquo()

| Function | Defuses | Returns | Use When |
|----------|---------|---------|----------|
| `expr()` | Your own code | Expression | Building expressions locally |
| `enquo()` | User's argument | Quosure | Forwarding function arguments |
| `enquos()` | User's `...` | List of quosures | Forwarding multiple arguments |

```r
# Defuse your own expression
my_expr  
#> expr: ^cyl + am
#> env:  global
```

### enquos() with .named

Auto-label unnamed arguments:

```r
g  [1] "cyl"   "1 + 1"

g(foo = cyl, bar = 1 + 1)
#> [1] "foo" "bar"
```

### Types of Defused Expressions

- **Calls**: `f(x, y)`, `1 + 1` - function invocations
- **Symbols**: `x`, `df` - named object references
- **Constants**: `1`, `"text"`, `NULL` - literal values

## Quosures

A quosure wraps an expression with its original environment. This is critical for correct evaluation when expressions travel across function and package boundaries.

### Why Environments Matter

```r
# In package A
my_function  dplyr::summarise({{ var }})
}

my_summarise  dplyr::summarise(!!enquo(var))
}
```

Use `{{` when you simply need to forward an argument. Use `enquo()` + `!!` when you need to inspect or transform the expression first.

### `!!` (Bang-Bang)

Injects a single expression:

```r
var  dplyr::summarise(mean(!!var))
#> Equivalent to: summarise(mean(cyl))

# Inject a value to avoid name collisions
x  dplyr::mutate(x = x / !!x)
#> Uses column x divided by env value 100
```

### `!!!` (Splice)

Injects each element of a list as separate arguments:

```r
vars  dplyr::select(!!!vars)
#> Equivalent to: select(cyl, am, vs)

# With enquos()
my_group_by  dplyr::group_by(!!!enquos(...))
}
```

### Where Operators Work

- **Data-masked arguments**: Implicitly enabled (dplyr, ggplot2, etc.)
- **inject()**: Explicitly enables operators in any context
- **Dynamic dots**: `!!!` and `{name}` work in functions using `list2()`

```r
# Enable injection in base functions
inject(
  with(mtcars, mean(!!sym("cyl")))
)
```

## Building Expressions from Data

### sym() and syms()

Convert strings to symbols:

```r
var  cyl

vars  [[1]]
#> cyl
#> [[2]]
#> am
```

### data_sym() and data_syms()

Create `.data$col` expressions (safer in tidy eval, avoids collisions):

```r
data_sym("cyl")
#> .data$cyl

data_syms(c("cyl", "am"))
#> [[1]]
#> .data$cyl
#> [[2]]
#> .data$am
```

Use `sym()` for base R functions; use `data_sym()` for tidy eval functions.

### Building Calls

```r
# With call()
call("mean", sym("x"), na.rm = TRUE)
#> mean(x, na.rm = TRUE)

# With expr() and injection
var  mean(x, na.rm = TRUE)
```

## Name Interpolation (Glue Operators)

In dynamic dots, use glue syntax for names.

### `{` for Variable Values

```r
name  # A tibble: 3 x 1
#>     foo
#>   
#> 1     1
#> 2     2
#> 3     3

tibble::tibble("prefix_{name}" := 1:3)
#> Column named: prefix_foo
```

### `{{` for Argument Labels

```r
my_mutate  dplyr::mutate("mean_{{ var }}" := mean({{ var }}))
}
mtcars |> my_mutate(cyl)
#> Creates column: mean_cyl
```

### englue() for String Formatting

```r
my_function  [1] "Column: some_column"
```

## Advanced: Manual Expression Transformation

When you need to modify expressions before injection:

```r
my_mean  dplyr::summarise(mean = !!wrapped)
}
```

For multiple arguments:

```r
my_mean  dplyr::summarise(!!!vars)
}
```

## Base R Equivalents

| rlang | Base R | Notes |
|-------|--------|-------|
| `expr()` | `bquote()` | bquote uses `.()` for injection |
| `enquo()` | `substitute()` | substitute returns naked expr, not quosure |
| `enquos(...)` | `eval(substitute(alist(...)))` | Workaround for dots |
| `!!` | `.()` in bquote | Only inside bquote |
| `eval_tidy()` | `eval()` | eval_tidy supports .data/.env pronouns |

## Pitfalls

### `{{` on Non-Arguments

`{{` should only wrap function arguments. On regular objects, it captures the value, not the expression:

```r
# Correct: var is a function argument
my_fn <- function(var) {{ var }}

# Problematic: x is not an argument
x <- 1
{{ x }}  # Returns 1, not the expression
```

### Operators Out of Context

Outside tidy eval/inject contexts, operators have different meanings:

| Operator | Intended | Outside Context |
|----------|----------|-----------------|
| `{{` | Embrace | Double braces (returns value) |
| `!!` | Inject | Double negation (logical) |
| `!!!` | Splice | Triple negation (logical) |

These fail silently. See the [tidy-evaluation](../tidy-evaluation/SKILL.md) skill for details on proper usage contexts.

## See Also

- **tidy-evaluation**: Programming patterns for data-masked functions
- **designing-tidy-r-functions**: Function API design principles
- **rlang-conditions**: Error handling with rlang

## Reference Files

- [topic-quosure.md](topic-quosure.md) - Complete quosure reference
- [topic-metaprogramming.md](topic-metaprogramming.md) - Advanced transformation patterns
- [topic-multiple-columns.md](topic-multiple-columns.md) - Multiple columns patterns

## Vignettes

Access detailed rlang documentation via R:

```r
# Defusing expressions
vignette("topic-defuse", package = "rlang")

# Injection operators
vignette("topic-inject", package = "rlang")

# Or browse all vignettes
browseVignettes("rlang")
```

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [jsperger](https://github.com/jsperger)
- **Source:** [jsperger/llm-r-skills](https://github.com/jsperger/llm-r-skills)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** yes

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: flagged — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-jsperger-llm-r-skills-metaprogramming
- Seller: https://agentstack.voostack.com/s/jsperger
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
