--- title: "Getting Started" output: arl::arl_html_vignette pkgdown: as_is: true vignette: > %\VignetteIndexEntry{Getting Started} %\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 that compiles to R. Every R function and data structure is available directly, and the macro system lets you transform code at compile time. This vignette walks through installing Arl, running the REPL, and writing your first expressions. ## Installation ```{r eval=FALSE} # install.packages("arl") devtools::install_github("wwbrannon/arl") ``` Arl is not on CRAN at the time of writing; install from GitHub with `devtools` as shown above. Once a CRAN release is available, the package can also be installed with the built-in `install.packages` function. If you have a local clone of the repository, `devtools::install()` from the repo directory will also work. ## Start the REPL ```{r eval=FALSE} engine <- arl::Engine$new() engine$repl() ``` ```{r, include=FALSE} engine <- arl::Engine$new() stopifnot(is.function(engine$repl)) ``` The engine loads the prelude automatically (though this [can be customized](#loading-stdlib-modules)), so functions like `when`, `let`, `map`, and `->` are available immediately. At the prompt, you can enter Arl expressions: ```{arl} (+ 1 2) (define x 10) (* x 2) ``` ```{arl, include=FALSE} (assert-equal 3 (+ 1 2)) (assert-equal 20 (* x 2)) ``` Type `(quit)` or press Ctrl+C to exit the REPL. **REPL options:** You can control the REPL with options before calling `engine$repl()`: `arl.repl_quiet` (minimal banner), `arl.repl_use_history` (set to `FALSE` to avoid touching R's readline history), and `arl.repl_bracketed_paste` (enable/disable bracketed paste mode for cleaner multiline pastes). See the discussion of [runtime options](runtime-options.html) for more. ## Run from the command line Run `arl::install_cli()` to see how to put the CLI wrapper on your PATH: ```{r eval=FALSE} arl::install_cli() #> Arl CLI wrapper script: /path/to/arl/bin/posix/arl #> #> To make it available on your PATH, create a symlink: #> #> mkdir -p ~/.local/bin #> ln -s "/path/to/arl/bin/posix/arl" ~/.local/bin/arl #> #> Then ensure ~/.local/bin is on your PATH. ``` Then you can evaluate code without opening R: ```bash arl --eval "(+ 1 2)" arl --file script.arl arl -q # quiet REPL (minimal banner) arl --help # see all options ``` You can also pass multiple files; they run in order in a shared engine, so definitions from earlier files are visible to later ones. Use `--no-stdlib` to start a bare engine without stdlib modules. ## Evaluate Arl from R The simplest way to evaluate Arl inside an R script is `eval_text` (or its alias `eval_string`), which reads and evaluates in one step: ```{r eval=FALSE} engine <- arl::Engine$new() engine$eval_text("(define x 10) (+ x 5)") engine$eval_string("(define y 20) (+ y 5)") ``` For finer control, you can parse and evaluate separately: ```{r eval=FALSE} exprs <- engine$read("(define x 10) (+ x 5)") engine$eval(exprs[[1]], exprs[[2]]) ``` ```{r, include=FALSE} engine <- arl::Engine$new() stopifnot(identical(engine$eval_text("(define x 10) (+ x 5)"), 15)) stopifnot(identical(engine$eval_string("(define y 20) (+ y 5)"), 25)) exprs <- engine$read("(define z 10) (+ z 5)") stopifnot(length(exprs) == 2L) stopifnot(identical(engine$eval(exprs[[1]], exprs[[2]]), 15)) ``` `eval()` accepts multiple expressions and evaluates them sequentially, returning the last value. ## Passing R data to the engine Use `$define()` to inject R objects into the engine so Arl code can use them: ```{r eval=FALSE} engine <- arl::Engine$new() engine$define("my_data", mtcars) engine$eval_text("(head my_data 3)") ``` ```{r, include=FALSE} engine <- arl::Engine$new() engine$define("my_data", mtcars) stopifnot(identical( engine$eval_text("(nrow my_data)"), nrow(mtcars) )) ``` Calls to `$define()` return the engine invisibly, so they can be chained: ```{r eval=FALSE} engine$define("x", 10)$define("y", 20)$eval_text("(+ x y)") ``` ```{r, include=FALSE} stopifnot(identical( engine$define("x", 10)$define("y", 20)$eval_text("(+ x y)"), 30 )) ``` To read results back into R, use `$eval_text()` (which returns the last value) or `$get_env()` to access the engine's environment directly. ## Run Arl files From the REPL, use `load` to run a file in the current environment so that its definitions are visible: ```{arl, eval=FALSE} (load "script.arl") ``` Use `run` to execute a file in an isolated child environment (definitions are not visible to later code): ```{arl, eval=FALSE} (run "script.arl") ``` ```{arl, include=FALSE} (define gs-load-path (tempfile :fileext ".arl")) (writeLines (c "(define gs-load-visible 99)") gs-load-path) (load gs-load-path) (assert-equal 99 gs-load-visible) (define gs-run-path (tempfile :fileext ".arl")) (writeLines (c "(define gs-run-only 123)") gs-run-path) (run gs-run-path) (assert-equal "missing" (try-catch gs-run-only (catch e "missing"))) (unlink gs-load-path) (unlink gs-run-path) ``` From R, you can mirror the two forms with `load_file_in_env`: ```{r eval=FALSE} engine <- Engine$new() # Like (load ...): definitions visible in the engine engine$load_file_in_env("script.arl") # Like (run ...): isolated; definitions not visible engine$load_file_in_env("script.arl", new.env(parent = engine$get_env())) ``` ```{r, include=FALSE} engine <- arl::Engine$new() script_path <- tempfile(fileext = ".arl") writeLines("(define gs-file-value 99)", script_path) script_path <- normalizePath(script_path, winslash = "/", mustWork = TRUE) engine$load_file_in_env(script_path) stopifnot(identical(engine$eval_text("gs-file-value"), 99)) engine2 <- arl::Engine$new() engine2$load_file_in_env(script_path, new.env(parent = engine2$get_env())) stopifnot(inherits(try(engine2$eval_text("gs-file-value"), silent = TRUE), "try-error")) unlink(script_path) ``` If you don't specify an environment, `load_file_in_env` uses the engine's toplevel environment. ## Loading stdlib modules The engine loads the **prelude** automatically — 10 modules (`logic`, `core`, `types`, `list`, `equality`, `functional`, `control`, `sequences`, `binding`, `threading`) whose exports are available immediately. Functions like `when`, `let`, `->`, and `try` come from the prelude and need no import. `do-list` requires `(import looping :refer :all)`. Non-prelude modules must be loaded explicitly with `import`. A bare `(import X)` makes the module available for qualified access (e.g. `math/inc`); add `:refer :all` to also import all exports unqualified: ```{arl, eval=FALSE} (import math) ; math/inc, math/dec, ... (import math :refer :all) ; inc/dec/abs/clamp/... (unqualified) (import looping :refer :all) ; loop/recur/until (import sort :refer :all) ; list-sort/sort-by/... (import strings :refer :all) ; string-split/string-join/... ``` Imports can also be selective or aliased: ```{arl, eval=FALSE} (import control :refer (when unless)) (import strings :as str) ; str/string-split, str/string-join, ... ``` Prelude modules are already available, but you can still import them explicitly in your own modules (which start with an empty scope plus prelude access): ```{arl, eval=FALSE} (import control :refer :all) ; when/unless/cond/case/try/try-catch/call-cc ``` ```{arl, include=FALSE} (import looping :refer :all) (assert-true (macro? 'when)) (assert-true (macro? 'unless)) (assert-true (macro? 'cond)) (assert-true (macro? 'let)) (assert-true (macro? 'loop)) (assert-true (macro? '->)) (assert-true (macro? 'try-catch)) ``` See [Modules and Imports](modules.html) for details on creating and importing modules, and the [Language Reference](lang-reference.html) for a list of stdlib modules and the full set of functions/macros each provides. ## R functions are available directly Because Arl compiles to R and its environment chain parents to R's `baseenv()`, every function in R's base package is available without any import. Functions like `max`, `min`, `sum`, `c`, `length`, `paste`, `lapply`, and hundreds of others work as-is: ```{arl} (max 1 5 3) (length (c 10 20 30)) (paste "hello" "world" :sep ", ") ``` ```{arl, include=FALSE} (assert-equal 5 (max 1 5 3)) (assert-equal 3 (length (c 10 20 30))) (assert-equal "hello, world" (paste "hello" "world" :sep ", ")) ``` Arl shadows some R names with its own versions (arithmetic operators are variadic, comparisons chain, `=` is equality not assignment). R's default packages (`stats`, `utils`, `grDevices`, `graphics`, `datasets`, `methods`) are also attached automatically, so functions like `median`, `head`, `lm`, and data like `iris` work without a prefix. See [Inherited R Functions](lang-reference.html#inherited-r-functions) in the Language Reference for details, and [R Interop and Data Workflows](r-interop.html) for calling R packages, using keyword arguments, and `r-eval`. ## Core syntax and semantics ### Truthiness Arl follows R's truthiness rules: `#f`/`FALSE`, `#nil`/`NULL`, and `0` are falsy; everything else is truthy. This differs from Scheme, where only `#f` is falsy. See [Troubleshooting](troubleshooting.html) for common pitfalls. ### Definitions and functions ```{arl} ; Define a variable (define greeting "hello") ; Define a function (define factorial (lambda (n) (if (< n 2) 1 (* n (factorial (- n 1)))))) (factorial 5) ``` ```{arl, include=FALSE} (assert-equal "hello" greeting) (assert-equal 120 (factorial 5)) ``` Use `unbind-variable` (which ultimately relies on R's `rm`) to remove a binding: ```{arl} (define tmp 42) (unbind-variable 'tmp (current-env)) ``` ```{arl, include=FALSE} (define ub-gs-test 1) (unbind-variable 'ub-gs-test (current-env)) (assert-equal "gone" (try-catch ub-gs-test (catch e "gone"))) ``` ### Local bindings ```{arl} ; let binds variables in a local scope (let ((x 10) (y 20)) (+ x y)) ``` ```{arl, include=FALSE} (assert-equal 30 (let ((x 10) (y 20)) (+ x y))) ``` ### Conditionals ```{arl} ; if is the basic conditional (if (> 3 2) "yes" "no") ; cond handles multiple branches ; the fallback #t case is like "else" (define describe (lambda (n) (cond ((< n 0) "negative") ((= n 0) "zero") (#t "positive")))) (describe 5) ; when is a one-armed conditional (no else branch) (when (> 3 2) (print "3 is greater")) ``` ```{arl, include=FALSE} (assert-equal "yes" (if (> 3 2) "yes" "no")) (assert-equal "positive" (describe 5)) (assert-equal "negative" (describe -1)) (assert-equal "zero" (describe 0)) ``` ### Sequencing ```{arl} ; begin evaluates expressions in order ; the last value is returned (begin (define a 1) (define b 2) (+ a b)) ``` ```{arl, include=FALSE} (assert-equal 3 (begin (define a 1) (define b 2) (+ a b))) ``` ### Lists and quoting Code and data in Arl, as in Lisp generally, are made of lists. You can define and manipulate lists: ```{arl} ; Comments start with semicolon (list 1 2 3) (car (list 1 2 3)) ; car = first element (cdr (list 1 2 3)) ; cdr = rest of list after car ``` ```{arl, include=FALSE} (assert-equal (list 1 2 3) (list 1 2 3)) (assert-equal 1 (car (list 1 2 3))) (assert-equal (list 2 3) (cdr (list 1 2 3))) ``` Lists typed in at the prompt are, by default, evaluated. To prevent evaluation, use the special form `quote` or its syntactic sugar `'`: ```{arl} (quote (+ 1 2)) ; => unevaluated expression (R language object / call) '(+ 1 2) ; simple alias for (quote ...) (list? '(+ 1 2)) (base::is.list '(+ 1 2)) ``` ```{arl, include=FALSE} (assert-equal '(+ 1 2) (quote (+ 1 2))) (assert-equal '(+ 1 2) '(+ 1 2)) (assert-true (list? '(+ 1 2))) (assert-false (base::is.list '(+ 1 2))) ``` This is an important Arl-vs-R distinction: `list?` follows Arl's Lisp-style semantics and treats quoted forms (which are [R call/language objects](https://cran.r-project.org/doc/manuals/r-devel/R-lang.html#Computing-on-the-language)) as lists, while R's `base::is.list` reports the underlying R object type and returns `FALSE` for calls. You can also perform selective evaluation with the `quasiquote` template syntax (written with the backtick `` ` ``), which is widely used in defining [macros](macros.html): ```{arl} ; Quasiquote allows selective evaluation with , and ,@ (define x 10) (define y (list 1 2 3)) `(list ,x 20 30) ; => (list 10 20 30) -- x is substituted, rest is literal `(list ,@y 20 30) ; => (list 1 2 3 20 30) -- y is spliced in at same level ``` ```{arl, include=FALSE} (define x 10) (define y (list 1 2 3)) (assert-equal '(list 10 20 30) `(list ,x 20 30)) (assert-equal '(list 1 2 3 20 30) `(list ,@y 20 30)) ``` For convenience, an Arl list is also an R list under the hood: ```{arl} (base::is.list (list 1 2 3)) ``` ```{arl, include=FALSE} (assert-true (base::is.list (list 1 2 3))) ``` Lisp's traditional pair lists made of cons cells are also supported, though they are implemented differently and less commonly used: ```{arl} (define pl (3 . (4 . 5))) ; this is a dotted-pair list (pair? pl) (list? pl) ; pair lists are a different kind of object ``` ```{arl, include=FALSE} (define pl (3 . (4 . 5))) (assert-true (pair? pl)) (assert-false (list? pl)) ``` For a concise guide to R-style lists vs dotted pairs and when to use each, see [Pairlists vs R lists](arl-vs-scheme.html#pairlists-vs-r-lists). For more on quoted forms as Arl lists but R language objects, see [Pairlists vs R lists](arl-vs-scheme.html#pairlists-vs-r-lists) and [R eval and calls](r-interop.html#r-eval). For more details on the standard library and macros, see the other vignettes in this package. ## Getting help Arl has a built-in help system. Use `help` with a string to look up any special form, macro, built-in, or stdlib function: ```{arl, eval=FALSE} (help "define") ; special form (help "when") ; macro -- shows docs and usage (help "map") ; stdlib function ``` ```{arl, include=FALSE} (assert-equal "ok" (try-catch (begin (help "define") "ok") (catch e "bad"))) (assert-equal "ok" (try-catch (begin (help "when") "ok") (catch e "bad"))) (assert-equal "ok" (try-catch (begin (help "map") "ok") (catch e "bad"))) ``` For functions or macros with documentation, `help` shows the signature, description, examples, and cross-references automatically. It also falls through to R's built-in help for R functions. ### Documenting your functions Arl provides two mechanisms for attaching documentation to functions and macros: - **`;;'` annotation comments** — Place roxygen-like tags (`@description`, `@examples`, `@seealso`, `@note`, etc) immediately before a `define` or `defmacro`. The compiler bakes the documentation in at compile time with no runtime overhead. This is the recommended approach for source files. - **`doc!`** — Attach or update documentation at runtime, useful for interactive work: `(doc! my-fn "Description here.")` or with keyword arguments like `(doc! my-fn :examples "(my-fn 3)")`. Both produce the same `arl_doc` attribute, so `help` and `doc` work identically regardless of how documentation was attached. See [Documenting Functions and Macros](documenting-functions-macros.html) for the full reference. ## Next steps - [Functions](functions.html) - [Modules and Imports](modules.html) - [Macros and Quasiquote](macros.html) - [R Interop and Data Workflows](r-interop.html) - [Examples](examples.html) - [Troubleshooting](troubleshooting.html) - [Arl Compared to Scheme](arl-vs-scheme.html)