Skip to contents

Examples on this page may reference functions from other stdlib modules without showing explicit (import ...) statements. In the REPL or your own code, you will need to import non-prelude modules before using their exports — see Importing modules.

Special forms

Special forms are expressions with evaluation rules that differ from normal function calls – for example, if does not evaluate all its arguments, and define binds a name rather than passing it as a value. They are handled directly by the compiler and cannot be redefined or passed as values.

  • quote – Return expr without evaluating it. The shorthand 'expr is equivalent.
  • if – Evaluate then or else based on test truthiness.
  • define – Bind a name in the current lexical environment.
  • set! – Update an existing binding in the current environment chain.
  • lambda – Create an anonymous function with lexical scope.
  • begin – Evaluate expressions in sequence and return the final result.
  • defmacro – Define a macro that transforms code before evaluation.
  • quasiquote – Build code/data templates with selective evaluation. The shorthand `expr is equivalent.
  • unquote – Evaluate expr inside a quasiquote template. Within a template, the shorthand ,expr is equivalent.
  • unquote-splicing – Splice list elements into a quasiquoted list. Within a quasiquote template, the shorthand ,@expr is equivalent.
  • and – Short-circuit logical conjunction.
  • or – Short-circuit logical disjunction.
  • while – Repeatedly evaluate body while condition remains truthy.
  • delay – Create a promise that delays evaluation of expr until forced.
  • import – Load a module and bind it as a first-class value. By default, (import name) binds the module environment to the symbol name for qualified access via name/sym. Use :refer to bring specific exports (or all exports) into scope unqualified. Use :as to alias the module binding.
  • module – Define a module with explicit exports. A named module registers itself in the module registry. A nameless module derives its name from the source file. export-all exports all non-private definitions; add :re-export to also re-export imported symbols.

Anything not in this list is a function or macro, whether built-in, standard library, user-defined, or inherited from R. Unlike special forms, these are ordinary values and can be passed around, stored in variables, and so on.

Built-in functions

Certain built-in functions are implemented in R (R/engine.R) rather than in Arl source modules. These are low-level primitives that need direct access to engine internals — cons-cell operations, the macro expander, the evaluator, promise handling, and documentation helpers. They are always available, even when the stdlib modules are not loaded (Engine$new(load_prelude = FALSE)).

Category Functions
Arithmetic +, *, -, /
Comparison <, <=, >, >=, =, ==, !=, not
List and Pair Predicates pair?
List Operations car, cdr, cons
Evaluation eval, read, write, load, r-eval
Documentation help, doc!, doc
Macro Utilities capture, gensym, macro?, macroexpand
Promises (Lazy Evaluation) promise?, force, promise-expr
Environment Introspection toplevel-env, builtins-env, current-env
Module Introspection module-ref, module?, namespace?, module-exports, module-name

These builtins are documented alongside the stdlib functions they relate to in the individual reference pages below.

Inherited R functions

Because Arl compiles to R and its environment chain ultimately parents to R’s baseenv(), every function in base R is available in Arl without any import or special syntax. This is not just interop glue — many common operations you will use day-to-day come directly from R rather than from Arl’s own builtins or stdlib.

What “inherited” means

When you write (max 1 2 3) in Arl, the compiler emits an R call to max(). There is no Arl wrapper — R’s own max function runs directly. The same is true for hundreds of base R functions. They work because R’s baseenv() sits at the bottom of the environment chain, so any name not shadowed by an Arl builtin, stdlib export, or user definition resolves to R’s version.

Examples of commonly used inherited functions

Here are some examples of base R functions that are used routinely in Arl code and are not redefined — R’s own implementations run directly:

Category Examples
Math max, min, sum, prod
Vectors c, length, seq, seq_len, seq_along, rep, rev, unique, which
Predicates is.null, is.na, is.numeric, is.character, is.logical, is.function, is.list, is.environment
Coercion as.numeric, as.character, as.logical, as.integer, as.double, as.list
Strings paste, paste0, sprintf, nchar, substr, sub, gsub, grepl, toupper, tolower, trimws, strsplit
Data structures list, vector, matrix, data.frame, names, attr, attributes
Accessors $, [, [[, @
Apply family lapply, sapply, vapply, mapply, tapply, do.call
I/O cat, message, warning, stop, readLines, writeLines, readRDS, saveRDS
Environment environment, new.env, parent.env, exists, assign, ls, rm

This is far from exhaustive — any function in R’s base package works in Arl the same way.

When Arl shadows R

Arl intentionally redefines some R names with its own versions. The most important are the operators:

  • Arithmetic (+, -, *, /): Arl’s versions are variadic, so (+ 1 2 3 4) works. R’s + is binary.
  • Comparison (<, <=, >, >=): Arl’s versions chain, so (< 1 2 3) means “1 < 2 and 2 < 3”. R’s < compares two vectors.
  • Equality (=, ==, !=): Arl’s versions are NULL-safe and variadic. R’s = is assignment, not comparison.
  • Logical (!): Arl uses not and the special forms and/or.
  • Control flow (if, while, for): These are Arl special forms or macros with Lisp-style syntax.

The stdlib also shadows some base R function names with Lisp-flavored versions:

  • Math wrappers (abs, sqrt, exp, log, floor, ceiling, round): Arl’s versions are thin wrappers that add documentation and integrate with the help system; behavior is the same.
  • List operations (append, sort, reverse): Arl’s versions work on both R lists and cons-cell pair lists, with Lisp-style semantics.
  • I/O and display (print, format, system): Arl’s versions add Lisp-style formatting or Arl-specific behavior.
  • Other (get, identity, subset, transform, try): Arl provides its own implementations of these with Arl-specific semantics.

When you need R’s original, use the base:: namespace prefix:

(base::sort (c 3 1 2))      ; R's vector sort, not Arl's list sort
(base::identity (list 1 2))  ; R's identity, not Arl's

Beyond base: R’s default packages

Arl’s environment chain parents to R’s baseenv(), not to .GlobalEnv. But R’s default packages — stats, utils, grDevices, graphics, datasets, and methods — are also attached at engine startup. Their exports are copied into a chain of environments between builtins_env and baseenv(), mirroring how R itself structures its search path.

This means functions like median, head, lm, plot, rgb, and data like iris and mtcars work without any prefix:

(median (c 1 2 3 4 5))
(head mtcars 3)
(lm (~ mpg cyl) :data mtcars)

The set of attached packages is controlled by R’s defaultPackages option (see ?options). Users can customize it by setting the R_DEFAULT_PACKAGES environment variable before starting R — for example, R_DEFAULT_PACKAGES="" disables all default packages, leaving only baseenv().

For packages not in the default set, use the :: prefix:

(jsonlite::fromJSON "{\"a\": 1}")
(httr::GET "https://example.com")

See R Interop and Data Workflows for more on calling R functions, using keyword arguments, formulas, and r-eval.

Standard library

In addition to built-in functions, Arl has a standard library written in Arl (inst/arl/*.arl). These stdlib modules provide various features: list operations, math, strings, control flow, and everything else. Modules are loaded in dependency order (each module declares its dependencies with (import ...) and is loaded after the modules it imports).

For the full, per-function reference, see the individual stdlib reference pages:

Importing modules

Prelude modules are loaded automatically by Engine$new(). Non-prelude modules (like math, looping, sort, strings, dict, set, io, etc.) require explicit (import ...). The import form is also needed inside your own modules (where you start with an empty scope) and when working with a bare engine (Engine$new(load_prelude = FALSE)):

; Import non-prelude modules
(import math)      ; inc/dec/abs/min/max/floor/ceiling/round/square/...
(import looping)   ; do-list/loop/recur/until
(import sort)      ; sort/sort-by
(import strings)   ; str/string-join/string-split/...

From R, you can create an engine with the stdlib already loaded:

engine <- Engine$new()                   # prelude loaded
bare <- Engine$new(load_prelude=FALSE)    # builtins only

Math and Numeric Functions

Arithmetic, comparison, rounding, trigonometry, number theory, and complex number utilities.

%, inc, dec, clamp, within?, signum, expt, quotient, remainder, modulo, gcd, lcm, make-rectangular, make-polar, real-part, imag-part, magnitude, angle

Modules: math.arl

Source files

Built-in functions are defined in R/engine.R. The Arl stdlib modules are organized by topic in inst/arl/ (each file defines a module). The engine loads these modules in dependency order when initializing.

If you’re looking for implementation details, these files are the source of truth for the stdlib definitions.