# Designing Tidy R Functions

> >

- **Type:** Skill
- **Install:** `agentstack add skill-jsperger-llm-r-skills-designing-tidy-r-functions`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **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/designing-tidy-r-functions

## Install

```sh
agentstack add skill-jsperger-llm-r-skills-designing-tidy-r-functions
```

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

## About

# Tidy R Function Design

Design R functions for humans, not computers. Optimize for cognitive load reduction, predictability, and composability. These principles apply to any R code, not just tidyverse packages.

**Core principle:** The less a user needs to think to use your function correctly, the better.

## Quick Reference

| Design Goal | Pattern |
|-------------|---------|
| Predictable names | Verb in imperative mood, prefixes for families |
| Clear arguments | Most important first, optional with defaults last |
| Pipe-friendly | Primary data as first argument |
| Type stability | Output type predictable from input types |
| Enumerated options | Use `arg_match()` with character vector defaults |
| Side effects | Return input invisibly; partition from computation |
| Complex strategies | Extract to strategy objects (not boolean flags) |

## Function Naming

### Use Verbs in Imperative Mood

```r
# Good: imperative verbs
mutate()
filter()
summarize()

# Exception: noun-y builders
geom_point()
recipe()
```

### Prefer Prefixes Over Suffixes

Prefixes enable autocomplete discovery:

```r
# Good: common prefix groups related functions
str_detect(), str_replace(), str_extract()
read_csv(), read_tsv(), read_delim()

# Suffixes for variations on a theme
map_int(), map_chr(), map_dbl()
```

### Length Inversely Proportional to Frequency

```r
# Very frequent -> short
c(), n(), df

# Less frequent -> descriptive
create_bootstrap_samples()
validate_model_specification()
```

## Argument Design

### Most Important Arguments First

```r
# Good: transformed data first (pipe-friendly)
str_replace(string, pattern, replacement)
left_join(x, y, by)

# Output-determining args early
read_csv(file, col_types, col_names)
```

### Required Arguments Have No Defaults

```r
# Good: required args have no defaults
my_function  # A tibble: 100 x 1
#>     .pred
#>     
#>  1   3.45
#>  2   2.89
```

### Side-Effect Functions Return Invisibly

Functions called for side effects should return the first argument invisibly:

```r
# Good: enables piping
write_csv 
  write_csv("backup.csv") |>
  filter(important) |>
  write_csv("filtered.csv")
```

## Side Effects

### Partition Side Effects from Computation

```r
# Bad: computation mixed with side effects
analyze <- function(x) {
  result <- expensive_computation(x)
  cat("Computed result:", result, "\n")  # side effect buried
  options(my_option = result)            # hidden state change
  result
}

# Good: side effects isolated
analyze <- function(x, verbose = FALSE) {
  result <- expensive_computation(x)
  if (verbose) cli::cli_inform("Computed result: {result}")
  result
}
```

### Make Side Effects Easy to Undo

Functions that change global state should return previous values:

```r
# Good: returns previous value for restoration
old <- options(digits = 3)
# ... do work ...
options(old)  # restore
```

## Strategy Patterns

### Avoid Boolean Strategy Flags

```r
# Bad: boolean flags for strategies
grepl(pattern, x, perl = TRUE, fixed = FALSE, ignore.case = TRUE)
# Which combinations are valid? What does perl + fixed mean?

# Good: strategy objects
str_detect(x, regex(pattern, ignore_case = TRUE))
str_detect(x, fixed(pattern))
```

### Strategy Objects for Complex Options

When strategies need different arguments, create helper functions:

```r
# Strategy helpers with strategy-specific arguments
regex <- function(pattern, ignore_case = FALSE, multiline = FALSE) {
  structure(list(pattern = pattern, ignore_case = ignore_case,
                 multiline = multiline), class = "regex")
}

fixed <- function(pattern) {
  structure(list(pattern = pattern), class = "fixed")
}

# Main function accepts strategy objects
str_detect <- function(string, pattern) {
  if (inherits(pattern, "regex")) {
    # regex-specific handling
  } else if (inherits(pattern, "fixed")) {
    # fixed-specific handling
  }
}
```

## Explicit Over Implicit

### Avoid Global Option Dependencies

```r
# Bad: behavior depends on global option
my_function <- function(x) {
  na_action <- getOption("na.action")  # implicit input
  # ...
}

# Good: explicit argument with informative default
my_function <- function(x, na_action = na.omit) {
  # ...
}
```

### Inform Users of Important Defaults

When defaults matter, tell the user:

```r
my_function <- function(x, tz = Sys.timezone()) {
  if (missing(tz)) {
    cli::cli_inform("Using timezone: {.val {tz}}")
  }
  # ...
}
```

## Model Object Design

### Minimize Stored Data

```r
# Bad: stores entire training set
model$training_data <- training_set  # memory bloat

# Good: store only what's needed for prediction
model$coefficients <- coefs
model$levels <- factor_levels
```

### Never Save Call Objects

Call objects can embed entire datasets and environments:

```r
# Bad: call may contain data
model$call <- match.call()

# Good: omit call or store only essential info
```

### Use Proper S3 Constructors

```r
# Constructor (internal)
new_my_model <- function(coefficients, levels) {
  structure(
    list(coefficients = coefficients, levels = levels),
    class = "my_model"
  )
}

# Validator (internal)
validate_my_model <- function(x) {
  stopifnot(is.numeric(x$coefficients))
  x
}

# Helper (user-facing)
my_model <- function(...) {
  result <- new_my_model(...)
  validate_my_model(result)
}
```

### Matrix Subsetting Discipline

Always preserve matrix structure:

```r
# Bad: may return vector
X[, 1]

# Good: always returns matrix
X[, 1, drop = FALSE]
```

## Design Review Checklist

When reviewing R function design:

- [ ] Function names are verbs in imperative mood (or nouns for builders)
- [ ] Related functions share a prefix
- [ ] Most important arguments come first
- [ ] Primary data is first argument (pipe-friendly)
- [ ] Required arguments have no defaults
- [ ] `...` comes between required and optional arguments
- [ ] String options use `arg_match()` with enumerated defaults
- [ ] Output type is predictable from input types
- [ ] Side-effect functions return input invisibly
- [ ] No hidden dependencies on global options or locale
- [ ] Strategy variations use objects, not boolean flags
- [ ] Model objects don't store training data or calls
- [ ] Matrix subsetting uses `drop = FALSE`

## Resources

- [Tidy Design Principles](https://design.tidyverse.org)
- [Tidymodels Implementation Principles](https://tidymodels.github.io/model-implementation-principles/)
- [Advanced R: S3](https://adv-r.hadley.nz/s3.html)

## 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:** no
- **Dynamic code execution:** no

*"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: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-jsperger-llm-r-skills-designing-tidy-r-functions
- 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%.
