miniR intentionally diverges from GNU R where R's behavior is confusing, inconsistent, or unnecessarily restrictive. This is not a drop-in replacement — it's a fresh implementation that respects R's useful semantics while fixing the bad ones.

Language Improvements

Trailing commas everywhere

GNU R rejects trailing commas in function calls and definitions:

# GNU R: Error in c(1, 2, 3, ) : argument 4 is empty
c(1, 2, 3,)

miniR allows trailing commas in all contexts — function calls, function definitions, c(), list(), data.frame(), etc. This matches the convention in most modern languages and eliminates diff noise when adding items to the end of a list.

# miniR: works fine
c(1, 2, 3,)
list(a = 1, b = 2,)
data.frame(x = 1:3, y = 4:6,)
function(a, b,) a + b

data.frame() forward references

GNU R evaluates each data.frame() column independently — later columns cannot reference earlier ones:

# GNU R: Error in data.frame(x = 1:5, xx = x * x) : object 'x' not found
data.frame(x = 1:5, xx = x * x)

miniR evaluates columns left-to-right in a child environment, so each named column is visible to subsequent column expressions. This matches the behavior of dplyr::tibble():

# miniR: works, xx = c(1, 4, 9, 16, 25)
data.frame(x = 1:5, xx = x * x)

# Chaining works too
data.frame(a = 1:3, b = a + 10L, c = a + b)

Column bindings do not leak into the caller's environment.

Unified pipe operators

In GNU R, |> (base) and %>% (magrittr) are different implementations with different features. miniR unifies them — |> and %>% are identical and both support _ and . as placeholders:

x |> f(a, _)      # works (R 4.2+ style)
x |> f(a, .)      # also works (magrittr style)
x %>% f(a, .)     # same thing — no library(magrittr) needed

The magrittr pipe variants are also native operators:

OperatorPurpose
%<>%Assignment pipe — pipe and assign back to LHS
%T>%Tee pipe — pipe for side effect, return original LHS
%$%Exposition pipe — expose LHS names to RHS expression

All available without library(magrittr).

if...else across newlines

GNU R requires braces when else is on a new line:

# GNU R: unexpected 'else'
if (TRUE) 1
else 2

miniR accepts this — else on a new line is unambiguous when preceded by a complete if expression.

** as power operator

miniR accepts ** as a synonym for ^ (exponentiation). GNU R does not recognize **.

# miniR: 8
2 ** 3

Parser Divergences

Newline continuation in postfix chains

x\n(y) parses as a call x(y), not two separate expressions. Same for x\n$y and pkg\n::foo. This is required for CRAN compatibility — many packages split chained expressions across lines.

? is top-level only

x <- ?sin doesn't work — ? is only at the top level of the precedence chain. In GNU R, ? can appear in expression position. Low priority since ? is interactive-only.

Semantic Differences

<<- creates in global, not parent

GNU R's <<- walks up the environment chain and creates the binding in the first enclosing environment where it exists, or the global environment if not found. miniR always creates missing bindings in the global environment.

Error messages

miniR aims for better error messages than GNU R — more informative, more specific, with suggestions for how to fix the problem. This means error message strings will not match GNU R exactly.

Tracebacks show C frames with source locations

GNU R tracebacks only show R-level call frames and opaque .Call entries:

Error in validate(x) : value must be non-negative, got -5
Calls: run_check -> validate -> .Call
Execution halted

miniR unwinds the native stack and shows individual C function frames with DWARF file:line info (when debug symbols are available):

Error: value must be non-negative, got -5
Traceback (most recent call last):
2: validate(x)
   [C] deep_helper at test.c:36 (stacktest.dylib)
   [C] middle_helper at test.c:42 (stacktest.dylib)
   [C] C_validate at test.c:47 (stacktest.dylib)
1: run_check(-5)

This makes debugging native code issues significantly easier — you can see exactly which C function errored and where, not just that .Call was invoked.

Apply Family

vapply is stricter about types

GNU R silently coerces integer results to double when FUN.VALUE = numeric(1). miniR rejects the type mismatch:

# GNU R: c(1, 2, 3) — silently coerces integer to double
vapply(1:3, function(x) x, numeric(1))

# miniR: error — "values must be type 'double', but result is type 'integer'"
# Fix: be explicit about the return type
vapply(1:3, function(x) as.double(x), numeric(1))  # works
vapply(1:3, function(x) x, integer(1))              # also works

This catches a common source of subtle bugs where the function returns an unexpected type.

vapply accepts fully named arguments

GNU R's vapply requires positional arguments for X, FUN, and FUN.VALUE. miniR also accepts them by name:

# Works in miniR, not in GNU R
vapply(X = letters[1:3], FUN = nchar, FUN.VALUE = integer(1))

sapply does not preserve input names

GNU R's sapply copies names from the input vector to the result by default (USE.NAMES = TRUE). miniR currently does not propagate names through sapply. This is a known gap, not a deliberate divergence.

lapply / Map / Reduce / Filter / Find / Position / Negate

These behave the same as GNU R. All are implemented, including extra-argument forwarding (lapply(x, f, extra_arg)).

Serialization

readRDS() / saveRDS() / load() / save() support GNU R's binary XDR format (version 2/3, gzip-compressed) for reading. The miniR-specific text format (miniRDS1) is also supported for round-tripping within miniR.

Not Yet Implemented

These are not divergences — just features that haven't landed yet:

  • postscript() device