---
title: "Arl Compared to Scheme"
output: arl::arl_html_vignette
pkgdown:
  as_is: true
vignette: >
  %\VignetteIndexEntry{Arl Compared to Scheme}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>")
arl::register_knitr_engine()
```

Arl is a Lisp dialect implemented in and with access to R. It borrows Lisp
syntax and macro conventions, but its runtime model and interop are rooted in
R. Because R's development was [heavily
influenced](https://cran.r-project.org/doc/html/interface98-paper/paper_1.html)
by Scheme and has [similar underlying
architecture](https://cran.r-project.org/doc/manuals/r-devel/R-lang.html#Computing-on-the-language),
this vignette highlights key similarities with Scheme and the most important
differences.

## Common ground

Arl and Scheme share a Lisp-family surface syntax and several familiar ideas:

- **S-expressions**: code and data use the same list syntax.
- **[Homoiconic](https://en.wikipedia.org/wiki/Homoiconicity) macros**:
  `defmacro` plus quasiquote/unquote are central.
- **First-class functions**: anonymous functions and higher-order patterns are
  idiomatic.
- **Lexical scoping**: bindings are local by default and resolve predictably.

## Core differences

The main differences come from Arl leaning on R's runtime:

### Evaluation model

Arl evaluates expressions via R's `eval()` and environments. That means:

- R's base functions are available without importing.
- R's evaluation and error semantics apply under the hood.

### Data model

Scheme has pairs and lists as the fundamental sequence type. Arl uses R data
structures:

- Lists are R lists or calls (R's type-generic vectors).
- Arl also has dotted pairs (R6 `Cons` objects, tested with `pair?`); `list-or-pair?` is true for non-empty lists or dotted pairs.
- Vectors are R vectors.
- `#nil` maps to R's `NULL`.

#### Pairlists vs R lists {#pairlists-vs-r-lists}

Scheme usually presents lists as chains of pairs. In Arl, the first distinction
is **representation**:

- **R list/call representation** (created by `list`, many stdlib ops, and R interop).
- **`Cons` cell representation** (R6 object), created by dotted-pair syntax / low-level pair construction.

Both can represent sequence-like data, but `list?` and `pair?` are intentionally
representation-oriented:

- `list?` is true for R lists/calls.
- `pair?` is true for `Cons` cells.
- `list-or-pair?` accepts either non-empty representation.

Because quoted forms are R calls, you may see:

```{arl}
(list? '(1 2 3))          ; => #t
(base::is.list '(1 2 3))  ; => #f
```

This is expected: Arl predicates express Lisp semantics; R predicates report
R object types.

For `Cons` chains, the classic proper/improper distinction still applies:

- A `Cons` chain ending in `()`/`#nil` is **proper**.
- A `Cons` chain with a non-list tail is **improper** (dotted pair).

`car`/`cdr` work across both representations; for an improper pair, `cdr`
returns the tail value directly.

```{arl}
(define rlist (list 1 2 3))
(define cons-proper (1 . (2 . (3 . ()))))
(define cons-improper (1 . 2))

(list? rlist)
(pair? cons-proper)
(pair? cons-improper)
(cdr rlist)          ; => (2 3)
(cdr cons-proper)    ; => (2 3) as a Cons chain
(cdr cons-improper)  ; => 2
```

```{arl, include=FALSE}
(define rlist (list 1 2 3))
(define cons-proper (1 . (2 . (3 . ()))))
(define cons-improper (1 . 2))

(assert-true (list? rlist))
(assert-false (pair? rlist))

(assert-true (pair? cons-proper))
(assert-false (list? cons-proper))
(assert-true (pair? cons-improper))
(assert-false (list? cons-improper))

(assert-equal (list 2 3) (cdr rlist))
(assert-equal 2 (cdr cons-improper))
(assert-true (null? (cdr (cdr (cdr cons-proper)))))
```

In practice, most everyday Arl code should prefer R-list-backed lists. `Cons`
representation remains useful for Lisp-style data modeling and explicit pair
work.

### Truthiness

Scheme typically treats only `#f` as false. Arl treats `#f`/`FALSE`,
`#nil`/`NULL`, and `0` as falsey; everything else is truthy.

### Numeric edge cases (R semantics)

Arl mirrors R's numeric behavior rather than Scheme's in several edge cases:

- **Division by zero**: `(/ 1 0)` yields `Inf` (and `(/ -1 0)` yields `-Inf`) instead of raising an error.
- **NaN comparisons**: `(== NaN NaN)` yields `NA` rather than `#f`, due to R's `NA` propagation rules.

This is intentional for R interop, but it is a meaningful semantic difference
from Scheme. Prefer predicates like `is.infinite`, `is.nan`, and `is.na` when
you need to branch on these values.

```{arl}
(/ 1 0)         ; => Inf
(== NaN NaN)    ; => NA
(is.infinite (/ 1 0))  ; => TRUE
(is.na (== NaN NaN))   ; => TRUE
```

```{arl, include=FALSE}
(assert-true (is.infinite (/ 1 0)))
(assert-true (is.na (== NaN NaN)))
```

### Numeric Tower Differences

Arl implements a numeric tower similar to Scheme's, but adapted for R's type system:

```
number?    (is.numeric OR is.complex)
├─ complex?  (is.complex)
└─ real?     (is.numeric AND NOT is.complex)
   ├─ ±Inf   (real but not rational)
   └─ rational? (real? AND is.finite)
      └─ integer? (is.finite AND is.numeric AND x == as.integer(x))
         └─ natural? (integer? AND x >= 0)

Orthogonal predicates:
exact?   (is.integer - storage type)
inexact? (number? AND NOT is.integer)
```

**Key differences from Scheme:**

1. R doesn't distinguish exact rationals from inexact reals - all are IEEE 754 floats
2. `rational?` means "finite real" in Arl (since all finite floats are rationally representable)
3. `exact?` checks storage type (integer vs. double), not mathematical exactness
4. All complex numbers in R are inexact (double-precision)

**Infinities and special values:**

```{arl}
(real? Inf)       ; => #t (infinities are real)
(rational? Inf)   ; => #f (but not rational)
(finite? Inf)     ; => #f
(real? NaN)       ; => #t
(finite? NaN)     ; => #f
```

```{arl, include=FALSE}
(assert-true (real? Inf))
(assert-false (rational? Inf))
(assert-false (finite? Inf))
(assert-true (real? NaN))
(assert-false (finite? NaN))
```

### Keywords and named arguments

Arl keywords (`:from`, `:to`) are self-evaluating and map to **named arguments**
in R calls. This is a major ergonomic difference from Scheme.

### Tail-call optimization

Scheme mandates full tail-call optimization (proper tail calls). Arl implements
**self-TCO**: the compiler detects `(define name (lambda ...))` or
`(set! name (lambda ...))` where the lambda body has self-calls in tail
position and rewrites them as `while` loops. This covers tail calls through
the `if` and `begin` special forms, as well as through macros in terms of them
like  `cond`, `let`, `let*`, `letrec`, etc. Because `letrec` expands into
`set!`, `letrec`-bound self-recursive lambdas are optimized automatically.

What is **not** covered:

- **Mutual recursion** (`f` calls `g` in tail position, `g` calls `f`).
- **`apply`-based tail calls** or indirect calls through higher-order functions.
- **Anonymous lambdas** which tail-recurse by use of a fixed-point combinator
  (no name for the compiler to detect self-calls against).

Like Scheme's proper tail calls, self-TCO elides recursive stack frames: on
error inside an optimized function, only the outermost call appears in the
stack trace rather than the full chain of recursive calls.

For cases not covered by self-TCO, use `loop`/`recur` for explicit
tail-recursive patterns.

```{arl}
;; Self-TCO optimizes this automatically
(define factorial
  (lambda (n acc)
    (if (< n 2) acc
        (factorial (- n 1) (* acc n)))))

;; loop/recur for explicit control
(loop ((i 10) (acc 1))
  (if (< i 2) acc
      (recur (- i 1) (* acc i))))
```

```{arl, include=FALSE}
(assert-equal 120 (factorial 5 1))
(assert-equal 3628800 (loop ((i 10) (acc 1))
  (if (< i 2) acc (recur (- i 1) (* acc i)))))
```

### Interop

Arl can call R functions directly:

```{arl}
(mean (c 1 2 3 4 5))
(seq :from 1 :to 10 :by 2)
```

```{arl, include=FALSE}
(assert-equal 3 (mean (c 1 2 3 4 5)))
```

Scheme code typically requires FFI layers for such interop; Arl treats it as
normal function application.

### Module system

Arl's module system is closer to Clojure's namespaces or Racket's modules than
to R5RS/R6RS libraries:

- **First-class modules**: modules are ordinary R environments and can be
  passed around, inspected, and stored in data structures.
- **Qualified access via `/`**: `(import math)` then `(math/inc 5)`, similar
  to Racket's prefix imports.
- **`:refer`, `:as`, `:rename` modifiers**: `(import strings :as str)` or
  `(import math :refer (inc dec))` — Clojure-inspired, unlike Scheme's
  `import` specs.
- **Prelude**: 10 core modules are loaded automatically (like Scheme's base
  library), while other stdlib modules require explicit `import`.

Scheme's R6RS library system is static and resolved at expansion time; Arl's
imports are evaluated at runtime and modules can be loaded conditionally.

## Macro systems

This is one of the most significant design differences between Arl and
Scheme.

### Scheme: pattern-based `syntax-rules`

Standard Scheme (R5RS and later) provides `syntax-rules`, a
pattern-matching macro system.  You write a set of patterns and
corresponding templates; the expander matches the input against the
patterns and substitutes into the template:

```scheme
;; Scheme syntax-rules (not valid Arl)
(define-syntax my-when
  (syntax-rules ()
    ((my-when test body ...)
     (if test (begin body ...) (void)))))
```

`syntax-rules` is **hygienic by construction**: because the expander
controls how names are substituted, macro-introduced bindings can never
accidentally shadow the caller's variables.  The trade-off is that macros
are limited to pattern-template rewriting -- they cannot perform
arbitrary computation at expansion time.  (Scheme also has
`syntax-case`, `er-macro-transformer`, and other lower-level systems,
but `syntax-rules` is the standard portable mechanism.)

### Arl: procedural `defmacro` with automatic hygiene

Arl uses **procedural `defmacro`-style** macros, closer to the model in
Common Lisp and Clojure.  Each macro is an ordinary function that
receives its arguments as unevaluated syntax trees and returns new
syntax.  Quasiquote, unquote, and unquote-splicing are the primary tools
for building the output.

```{arl}
(defmacro my-when (test . body)
  `(if ,test (begin ,@body) #nil))

(my-when (> 5 3) (+ 1 1))
```

```{arl, include=FALSE}
(assert-equal 2 (my-when (> 5 3) (+ 1 1)))
```

Because the macro body is ordinary Arl code, macros can do arbitrary
computation at expansion time -- loops, conditionals, list manipulation,
even calling R functions -- before returning the final syntax.  This is
more flexible than `syntax-rules`, which is restricted to pattern
matching and template substitution.

### Hygiene: different approaches to the same goal

Traditional `defmacro` (Common Lisp-style) is **unhygienic**: the macro
author must manually use `gensym` to avoid name collisions.  Scheme's
`syntax-rules` is hygienic by construction.

Arl splits the difference.  Its `defmacro` is **hygienic by default**:
bindings the macro introduces (via `define`, `let`, `lambda`, etc.) are
automatically renamed so they cannot collide with caller names.

```{arl}
(defmacro swap (a b)
  `(let ((temp ,a))
     (set! ,a ,b)
     (set! ,b temp)))

(define temp 999)
(define p 1)
(define q 2)
(swap p q)
(list p q temp)  ; temp is unaffected
```

```{arl, include=FALSE}
(assert-equal (list 2 1 999) (list p q temp))
```

When you *want* to introduce a binding visible to the caller (an
anaphoric macro), you use `capture` to opt out of hygiene for specific
symbols.  This is something `syntax-rules` cannot express at all without
dropping down to a lower-level system.

```{arl}
(defmacro aif2 (test then alt)
  `(let ((it ,test))
     (if it ,(capture 'it then) ,(capture 'it alt))))

(aif2 (+ 10 20) (string-concat "got " it) "none")
```

```{arl, include=FALSE}
(assert-equal "got 30" (aif2 (+ 10 20) (string-concat "got " it) "none"))
```

### Summary of differences

| | Scheme `syntax-rules` | Arl `defmacro` |
|---|---|---|
| **Style** | Pattern-template rewriting | Procedural (arbitrary code) |
| **Hygiene** | By construction | Automatic, opt-out via `capture` |
| **Expansion-time computation** | Not supported | Full language available |
| **Anaphoric macros** | Requires `syntax-case` or lower-level system | `capture` built in |
| **Quasiquote** | Not used in macros (template syntax instead) | Primary code-building tool |

For more on writing macros in Arl, see the [Macros and
Quasiquote](macros.html) vignette.

## Special forms

Many familiar special forms exist (`quote`, `if`, `define`, `lambda`, `begin`),
and there are Arl-specific R interop operators (`~`, `::`, `:::` for formula
definition and package access) plus macro helpers tuned for R interop. See the [language
reference](lang-reference.html) for more details.

## When to think "Scheme" vs "R"

- **Think Scheme** for macro structure and list processing patterns.
- **Think R** for data frames, formulas, statistical modeling, and named
  argument calls.

## Related guides

- [Getting Started](getting-started.html)
- [Macros and Quasiquote](macros.html)
- [R Interop and Data Workflows](r-interop.html)
