Language guide

A tour with a small example at every step. The REPL, scripts, and packages load library forms such as lambda and let automatically.

1. Values & quotes

Numbers, booleans, strings, and #inert are self-evaluating. Symbols look up bindings. Quote freezes a tree.

Literals

42
#t
"hello"
#inert
'foo
'(1 2 3)
42 · #t · "hello" · #inert · foo · (1 2 3)

2. define & if

define and if are primitive operatives: they control evaluation of their operands.

Bind and branch

(define answer 42)
(if (< answer 100)
  'small
  'huge)
small

3. Lists & vectors

Pairs use cons/car/cdr. Improper lists write (a & b). Vectors use […] or vector.

Structure

(cons 1 (cons 2 ()))
(car '(a b c))
(define v (vector 10 20 30))
(vector-ref v 1)
(vector-set! v 1 99)
(vector-ref v 1)
(1 2) · a · 20 · #inert · 99

4. vau & lambda

vau builds operatives. lambda (stdlib) wraps a vau so arguments evaluate first — the usual function calling convention.

Operative vs applicative

(define raw (vau (x) _ x))
(raw (+ 1 2))          ; ⇒ (+ 1 2)

(define add (lambda (x y) (+ x y)))
(add 3 4)              ; ⇒ 7

((λ (x) (* x x)) 8)    ; ⇒ 64

eval in the caller’s environment

(define force-it (vau (x) e (eval e x)))
(define n 21)
(force-it (+ n n))
; ⇒ 42

5. Control forms from the library

cond, and?, and or? are operatives — they short-circuit safely.

Short-circuit

(and? #f (/ 1 0))   ; ⇒ #f  (no division)
(or?  #t (/ 1 0))   ; ⇒ #t
(cond
  ((<= 1 0) 'nope)
  ((eqv? 1 1) 'yes))
#f · #t · yes

6. The let family

let / let* / letrec

(let ((x 2) (y 3)) (* x y))
; ⇒ 6

(let* ((x 3) (y x)) (+ x y))
; ⇒ 6

(letrec ((sum (lambda (n)
                (if (zero? n) 0 (+ n (sum (- n 1)))))))
  (sum 5))
; ⇒ 15

7. First-class environments

Bundle bindings, evaluate remotely, or import names into the current environment.

bindings→environment & remote-eval

(define e (bindings->environment (x 10) (y 20)))
(remote-eval x e)
; ⇒ 10

(import! e x)
x
; ⇒ 10

8. Continuations

Full call/cc and delimited shift/reset are native.

Escape with call/cc

(call/cc (lambda (k) (* 5 (k 4))))
; ⇒ 4

Generator-style yield

(defn (yield x)
  (shift (lambda (k) (cons x (k (#inert))))))

(reset (begin (yield 1) (yield 2) (yield 3) ()))
; ⇒ (1 2 3)

9. .NET interop

new, . (methods), .get / .set (fields & properties). Static members use a type name atom; instance members use an object value.

CLR objects

(define id (. System.Guid NewGuid))
(. System.Console WriteLine
   (. System.String Format "id={0}" id))

(define path
  (. System.IO.Path Combine
     (. System.IO.Path GetTempPath)
     "hello.txt"))
(. System.IO.File WriteAllText path "IronKernel")
(. System.IO.File ReadAllText path)

10. Invent your own syntax

This is Kernel’s punchline: grow the language with vau.

trace — print source, then run it

(define trace
  (vau (exp) env
    (begin
      (show exp)
      (. System.Console WriteLine "")
      (eval env exp))))

(trace (+ 2 2))
; prints: (+ 2 2)
; ⇒ 4

timed — no thunks required

(define timed
  (vau (label exp) env
    (let* ((start (.get System.DateTime Now))
           (result (eval env exp))
           (ms (.get (- (.get System.DateTime Now) start)
                     TotalMilliseconds)))
      (printf "[{0}] {1} ms\n" label ms)
      result)))

(timed "work" (letrec ((f (lambda (n)
                            (if (zero? n) 0 (+ n (f (- n 1)))))))
                (f 200)))

See the full sample in Examples/vau-dotnet.scm, then keep operators.html open as a reference.