Janet 1.19.0-a0ddfcb Documentation
(Other Versions: 1.18.1 1.17.1 1.16.1 1.15.0 1.13.1 1.12.2 1.11.1 1.10.1 1.9.1 1.8.1 1.7.0 1.6.0 1.5.1 1.5.0 1.4.0 1.3.1 )

Special Forms

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

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 cannot be expressed through macros or functions. Special forms can be thought of as forming the real 'core' language of Janet. There are only 13 special forms in Janet.

(def name meta... value)

This special form binds a value to a symbol. The symbol can be substituted for the value in subsequent expressions 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.
 (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 it 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 introduce 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.
(fn [w x y z &] (tuple w w x x y y z z))

For more information on functions, see the Functions section.

(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
 (def a 1)
 (print a))

# a is not defined here, so fails

(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 allows an array or tuple to be put into another form inline. It only has an effect in two places - as an argument in a function call or literal constructor, or as the argument to the unquote form. Outside of these two settings, the splice special form simply evaluates directly to its argument x. The shorthand for splice is prefixing a form with a semicolon. The splice special form has no effect on the behavior of other special forms, except as an argument to unquote.

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

[;(range 100)] # First 100 integers in a tuple instead of an array.

(def ;[a 10]) # this will not work as def is a special form.

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

A splice form can also be used as the argument to an unquote form, where it will behave like an unquote-splicing special in Common Lisp. Using the short form of both of these specials, this can be abbreviated ,;some-array-expression.

(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. Therefore, it is expected that the body will contain some side effects or the loop will go on forever. 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 the 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 handwritten 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
 (if (= x :one) (break))
 (if (= x :three) (break x))
 (if (and (number? x) (even? x)) (break))
 (print "x = " x)

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

(set l-value r-value)

Update the value of the var l-value with the new 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.

(upscope & body)

Similar to do, upscope evaluates a number of forms and sequence and evaluates to the result of the last form. However, upscope does not create a new lexical scope, which means that bindings created inside it are visible in the scope where upscope is declared. This is useful for writing macros that make several def and var declarations at once.

 (def a 1)
 (def b 2)
 (def c 3)) # -> 3

(+ a b c) # -> 6

In general, use this macro as a last resort. There are other, often better ways to do this, including using destructuring.

(def [a b c] [1 2 3])