Janet 1.3.0 Documentation

Special Forms

This document serves as an overview of all of the special forms in Janet.

Janet is a lisp and so is defined in terms of mostly S-expressions, or in terms of Janet, tuples. Tuples are used to represent function calls, macros, and special forms. Most functionality is exposed through functions, some through macros, and a minimal amount through special forms. Special forms are neither functions nor macros — they are used by the compiler to directly express a low level construct that can not be expressed through macros or functions. Special forms can be thought of as forming the real 'core' language of janet.

(def name meta... value)

This special form binds a value to a symbol. The symbol can be substituted for the value in subsequent expression for the same result. A binding made by def is a constant and cannot be updated. A symbol can be redefined to a new value, but previous uses of the binding will refer to the previous value of the binding.

(def anumber (+ 1 2 3 4 5))

(print anumber) # prints 15

Def can also take a tuple, array, table or struct to perform destructuring on the value. This allows us to do multiple assignments in one def.

(def [a b c] (range 10))
(print a " " b " " c) # prints 0 1 2

(def {:x x} @{:x (+ 1 2)})
(print x) # prints 3

(def [y {:x x}] @[:hi @{:x (+ 1 2)}])
(print y x) # prints hi3

Def can also append metadata and a docstring to the symbol when in the global scope. If not in the global scope, the extra metadata will be ignored.

(def mydef :private 3) # Adds the :private key to the metadata table.
(def mydef2 :private "A docstring" 4) # Add a docstring

# The metadata will be ignored here because mydef is
# not accessible outside of the do form.
(do
 (def mydef :private 3)
 (+ mydef 1))

(var name meta... value)

Similar to def, but bindings set in this manner can be updated using set. In all other respects is the same as def.

(var a 1)
(defn printa [] (print a))

(printa) # prints 1
(++ a)
(printa) # prints 2
(set a :hi)
(printa) # prints hi

(fn name? args body...)

Compile a function literal (closure). A function literal consists of an optional name, an argument list, and a function body. The optional name is allowed so that functions can more easily be recursive. The argument list is a tuple of named parameters, and the body is 0 or more forms. The function will evaluate to the last form in the body. The other forms will only be evaluated for side effects.

Functions also introduced a new lexical scope, meaning the defs and vars inside a function body will not escape outside the body.

(fn []) # The simplest function literal. Takes no arguments and returns nil.
(fn [x] x) # The identity function
(fn identity [x] x) # The name will make stacktraces nicer
(fn [] 1 2 3 4 5) # A function that returns 5
(fn [x y] (+ x y)) # A function that adds its two arguments.

(fn [& args] (length args)) # A variadic function that counts its arguments.

# A function that doesn't strictly check the number of arguments.
# Extra arguments are ignored, and arguments not passed are nil.
(fn [w x y z &] (tuple w w x x y y z z))

(do body...)

Execute a series of forms for side effects and evaluates to the final form. Also introduces a new lexical scope without creating or calling a function.

(do 1 2 3 4) # Evaluates to 4

# Prints 1, 2 and 3, then evaluates to (print 3), which is nil
(do (print 1) (print 2) (print 3))

# Prints 1
(do
 (def a 1)
 (print a))

# a is not defined here, so fails
a

(quote x)

Evaluates to the literal value of the first argument. The argument is not compiled and is simply used as a constant value in the compiled code. Preceding a form with a single quote is shorthand for `(quote expression)`.

(quote 1) # evaluates to 1
(quote hi) # evaluates to the symbol hi
(quote quote) # evaluates to the symbol quote

'(1 2 3) # Evaluates to a tuple (1 2 3)
'(print 1 2 3) # Evaluates to a tuple (print 1 2 3)

(if condition when-true when-false?)

Introduce a branching construct. The first form is the condition, the second form is the form to evaluate when the condition is true, and the optional third form is the form to evaluate when the condition is false. If no third form is provided it defaults to nil.

The if special form will not evaluate the when-true or when-false forms unless it needs to - it is a lazy form, which is why it cannot be a function or macro.

The condition is considered false only if it evaluates to nil or false - all other values are considered true.

(if true 10) # evaluates to 10
(if false 10) # evaluates to nil
(if true (print 1) (print 2)) # prints 1 but not 2
(if 0 (print 1) (print 2)) # prints 1
(if nil (print 1) (print 2)) # prints 2
(if @[] (print 1) (print 2)) # prints 1

(splice x)

The splice special form is an interesting form that doesn't have an analog in most lisps. It only has an effect in two places - as an argument in a function call, or as the argument to the unquote form. Outside of these two settings, the splice special form simply evaluates directly to it's argument x. The shorthand for splice is prefixing a form with a semicolon.

In the context of a function call, splice will insert the contents of x in the parameter list.

(+ 1 2 3) # evaluates to 6

(+ @[1 2 3]) # bad

(+ (splice @[1 2 3])) # also evaluates to 6

(+ ;@[1 2 3]) # Same as above

(+ ;(range 100)) # Sum the first 100 natural numbers

(+ ;(range 100) 1000) # Sum the first 100 natural numbers and 1000

Notice that this means we rarely will need the apply function, as the splice operator is more flexible.

The splice operator can also be used inside an unquote form, where it will behave like an unquote-splicing special in other lisps.

(while condition body...)

The while special form compiles to a C-like while loop. The body of the form will be continuously evaluated until the condition is false or nil. Therefor, it is expected that the body will contain some side effects of the loop will go on for ever. The while loop always evaluates to nil.

(var i 0)
(while (< i 10)
 (print i)
 (++ i))

(break value?)

Break from a while loop or return early from a function. The break special form can only break from the inner-most loop. Since a while loop always returns nil, the optional `value` parameter has no effect when used in a while loop, but when returning from a function, the value parameter is function's return value.

The break special form is most useful as a low level construct for macros. You should try to avoid using it in hand written code, although it can be very useful for handling early exit conditions without requiring deeply indented code (try the cond macro first, though).

# Breaking from a while loop
(while true
 (def x (math/random))
 (if (> x 0.95) (break))
 (print x))
# Early exit example using (break)
(fn myfn
 [x]
 (if (= x :one) (break))
 (if (= x :three) (break x))
 (if (and (number? x) (even? x)) (break))
 (print "x = " x)
 x)

# Example using (cond)
(fn myfn
 [x]
 (cond
  (= x :one) nil
  (= x :three) x
  (and (number? x) (even? x)) nil
  (do
   (print "x = " x)
   x)))

(set l-value r-value)

Update the value of a var l-value to a new value r-value. The set special form will then evaluate to r-value.

The r-value can be any expression, and the l-value should be a bound var or a pair of a data structure and key. This allows set to behave like setf or setq in Common Lisp.

(var x 10)
(defn prx [] (print x))
(prx) # prints 10
(set x 11)
(prx) # prints 11
(set x nil)
(prx) # prints nil

(def tab @{})
(set (tab :property) "hello")
(pp tab) # prints @{:property "hello"}

(quasiquote x)

Similar to (quote x), but allows for unquoting within x. This makes quasiquote useful for writing macros, as a macro definition often generates a lot of templated code with a few custom values. The shorthand for quasiquote is a leading tilde ~ before a form. With that form, (unquote x) will evaluate and insert x into the unquote form. The shorthand for (unquote x) is ,x.

(unquote x)

Unquote a form within a quasiquote. Outside of a quasiquote, unquote is invalid.