--- title: "Functions" output: arl::arl_html_vignette pkgdown: as_is: true vignette: > %\VignetteIndexEntry{Functions} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set(collapse = TRUE, comment = "#>") arl::register_knitr_engine() ``` This vignette covers how to define, call, and compose functions in Arl. ## Defining Functions Functions are created with `lambda` and bound to names with `define`: ```{arl} (define double (lambda (x) (* x 2))) (double 5) ``` ```{arl, include=FALSE} (assert-equal 10 (double 5)) ``` **Important:** Unlike Scheme, `(define (f x) body)` is **not** function shorthand in Arl. Because `define` supports [destructuring](#destructuring), writing `(define (f x) body)` tries to destructure the value `body` into the pattern `(f x)` — it does not create a function. Always use the explicit `define` + `lambda` form shown above. ```{arl, eval=FALSE} ;; WRONG — this is destructuring, not function definition (define (f x) (+ x 1)) ;; CORRECT (define f (lambda (x) (+ x 1))) ``` ```{arl, include=FALSE} ;; Verify the wrong pattern errors (destructuring bind mismatch) (assert-error (eval '(define (f x) (+ x 1)))) ``` ## Parameter Features {#parameter-features} Arl's `lambda` supports several parameter styles. These work identically in `defmacro` parameters. ### Required parameters ```{arl} (define add (lambda (a b) (+ a b))) (add 3 4) ``` ```{arl, include=FALSE} (assert-equal 7 (add 3 4)) ``` ### Optional parameters with defaults Wrap a parameter in a pair `(name default)`: ```{arl} (import strings :refer (string-append)) (define greet (lambda ((name "world")) (string-append "hello, " name))) (greet) ; uses default (greet "Alice") ; overrides default ``` ```{arl, include=FALSE} (assert-equal "hello, world" (greet)) (assert-equal "hello, Alice" (greet "Alice")) ``` ### Rest parameters Use `.` to collect remaining arguments into a list: ```{arl} (define sum-all (lambda (first . rest) (reduce + (cons first rest)))) (sum-all 1 2 3 4) ``` ```{arl, include=FALSE} (assert-equal 10 (sum-all 1 2 3 4)) ``` ### Destructuring parameters {#destructuring-parameters} Use `(pattern ...)` to destructure an argument: ```{arl} (define first-of-pair (lambda ((pattern (a b))) a)) (first-of-pair (list 10 20)) ``` ```{arl, include=FALSE} (assert-equal 10 (first-of-pair (list 10 20))) ``` Patterns can be nested, have defaults, or combine with rest parameters: ```{arl} (define point-sum (lambda ((pattern (x y) (list 0 0))) (+ x y))) (point-sum) ; uses default (0 0) (point-sum (list 3 4)) ; => 7 ``` ```{arl, include=FALSE} (assert-equal 0 (point-sum)) (assert-equal 7 (point-sum (list 3 4))) ``` ### Combining parameter styles You can mix required, optional, destructuring, and rest parameters: ```{arl} (define flexible (lambda (required (opt 10) . rest) (list required opt rest))) (flexible 1) ; => (1 10 ()) (flexible 1 2 3 4) ; => (1 2 (3 4)) ``` ```{arl, include=FALSE} (assert-equal (list 1 10 (list)) (flexible 1)) (assert-equal (list 1 2 (list 3 4)) (flexible 1 2 3 4)) ``` ## Destructuring {#destructuring} Destructuring lets you unpack a data structure into individual variables in a single `define` statement. Instead of binding a name to a value, you provide a **pattern** — a nested list of names — and Arl matches each name to the corresponding element of the value: ```{arl} (define (x y z) (list 10 20 30)) (list x y z) ``` ```{arl, include=FALSE} (assert-equal 10 x) (assert-equal 20 y) (assert-equal 30 z) ``` This works with nested structures too: ```{arl} (define (p (q r)) (list 1 (list 2 3))) (list p q r) ``` ```{arl, include=FALSE} (assert-equal 1 p) (assert-equal 2 q) (assert-equal 3 r) ``` ### `destructuring-bind` The `destructuring-bind` macro provides a scoped form of destructuring. It binds a pattern to a value and evaluates body forms with those bindings in scope: ```{arl} (destructuring-bind (first second . rest) (list 1 2 3 4 5) (list first second rest)) ``` ```{arl, include=FALSE} (assert-equal (list 1 2 (list 3 4 5)) (destructuring-bind (first second . rest) (list 1 2 3 4 5) (list first second rest))) ``` This is what macros like `let*` and `when-let` use under the hood — each binding in a `let` form is a destructuring bind, so patterns work anywhere a `let` binding does: ```{arl} (let (((a b) (list 1 2)) ((c d) (list 3 4))) (+ a b c d)) ``` ```{arl, include=FALSE} (assert-equal 10 (let (((a b) (list 1 2)) ((c d) (list 3 4))) (+ a b c d))) ``` ### Destructuring in function parameters As shown in [Parameter Features](#parameter-features) above, `lambda` parameters can also destructure their arguments using the `(pattern ...)` syntax. See [Destructuring parameters](#destructuring-parameters) for examples. ## Calling Functions ### Positional arguments ```{arl} (define add (lambda (a b) (+ a b))) (add 3 4) ``` ```{arl, include=FALSE} (assert-equal 7 (add 3 4)) ``` ### Keyword arguments Keywords (`:name value`) pass named arguments. This is especially useful when calling R functions: ```{arl} (seq :from 1 :to 5) ``` ```{arl, include=FALSE} (assert-equal (c 1 2 3 4 5) (seq :from 1 :to 5)) ``` Keywords also work with Arl-defined functions — the keyword name is matched to the parameter name: ```{arl} (define make-point (lambda (x y) (list x y))) (make-point :y 20 :x 10) ``` ```{arl, include=FALSE} (assert-equal (list 10 20) (make-point :y 20 :x 10)) ``` See [R Interop](r-interop.html#keyword-syntax) for details on keyword syntax and quoting. ## Local Functions Use `let`, `let*`, and `letrec` to bind functions in local scope. ### `let` / `let*` for simple local functions ```{arl} (let ((double (lambda (x) (* x 2))) (inc (lambda (x) (+ x 1)))) (double (inc 3))) ``` ```{arl, include=FALSE} (assert-equal 8 (let ((double (lambda (x) (* x 2))) (inc (lambda (x) (+ x 1)))) (double (inc 3)))) ``` ### `letrec` for recursive local functions `letrec` allows bindings to refer to each other, which is necessary for local recursive or mutually-recursive functions: ```{arl} (letrec ((even? (lambda (n) (if (= n 0) #t (odd? (- n 1))))) (odd? (lambda (n) (if (= n 0) #f (even? (- n 1)))))) (list (even? 10) (odd? 7))) ``` ```{arl, include=FALSE} (assert-equal (list #t #t) (letrec ((even? (lambda (n) (if (= n 0) #t (odd? (- n 1))))) (odd? (lambda (n) (if (= n 0) #f (even? (- n 1)))))) (list (even? 10) (odd? 7)))) ``` Because `letrec` expands into `set!`, self-recursive `letrec` lambdas are [automatically tail-call optimized](tail-call-optimization.html). (Note that mutually recursive functions are not!) ## Higher-Order Functions Arl's standard library provides the usual higher-order toolkit: ```{arl} (map (lambda (x) (* x x)) (list 1 2 3 4)) (filter even? (list 1 2 3 4 5 6)) ``` ```{arl, include=FALSE} (assert-equal (list 1 4 9 16) (map (lambda (x) (* x x)) (list 1 2 3 4))) (assert-equal (list 2 4 6) (filter even? (list 1 2 3 4 5 6))) ``` ```{arl} (define add5 (partial + 5)) (add5 10) (define abs-then-double (compose (lambda (x) (* x 2)) abs)) (abs-then-double -3) ``` ```{arl, include=FALSE} (assert-equal 15 (add5 10)) (assert-equal 6 (abs-then-double -3)) ``` See [Standard Library: Higher-Order Functions](lang-functional.html) for the full reference including `reduce`, `curry`, `juxt`, `memoize`, and more. ## Recursion ### Self-TCO (automatic) When you define a named function that calls itself in tail position, the compiler automatically rewrites it as a loop — no stack overflow: ```{arl} (define factorial (lambda (n acc) (if (< n 2) acc (factorial (- n 1) (* acc n))))) ;; No stack overflow (but 100000! is too large ;; to be representable and overflows to Inf) (factorial 100000 1) ``` ```{arl, include=FALSE} (assert-equal 120 (factorial 5 1)) ``` ### `loop` / `recur` For explicit looping or patterns where self-TCO does not apply, use `loop`/`recur`: ```{arl} (import looping :refer (loop recur)) (loop ((i 5) (acc 1)) (if (< i 2) acc (recur (- i 1) (* acc i)))) ``` ```{arl, include=FALSE} (assert-equal 120 (loop ((i 5) (acc 1)) (if (< i 2) acc (recur (- i 1) (* acc i))))) ``` See [Tail Call Optimization](tail-call-optimization.html) for details on what counts as tail position and how the optimization works. ## Macros vs Functions Macros use the same parameter syntax (required, optional, rest, destructuring) but operate on **unevaluated syntax** at compile time rather than on runtime values. To define a macro, use `defmacro` instead of `define` and `lambda`: ```{arl, eval=FALSE} ;; Function: runs at eval time, receives evaluated args, returns new value (define double (lambda (x) (* x 2))) ;; Macro: runs at compile time, receives unevaluated syntax, returns new syntax (defmacro when (test . body) `(if ,test (begin ,@body) #nil)) ``` See [Macros and Quasiquote](macros.html) for the full guide. ## Related guides - [Getting Started](getting-started.html) - [Macros and Quasiquote](macros.html) - [Tail Call Optimization](tail-call-optimization.html) - [Standard Library: Higher-Order Functions](lang-functional.html) - [R Interop and Data Workflows](r-interop.html) - [Arl Compared to Scheme](arl-vs-scheme.html)