Janet 1.42.0-dev-7fd75f3 Documentation
(Other Versions: 1.42.0 1.41.2 1.41.1 1.40.1 1.40.0 1.39.1 1.38.0 1.37.1 1.36.0 1.35.0 1.34.0 1.31.0 1.29.1 1.28.0 1.27.0 1.26.0 1.25.1 1.24.0 1.23.0 1.22.0 1.21.0 1.20.0 1.19.0 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 )

Bindings (def and var)

The def special form binds a symbol for later use. This kind of association between a symbol and a value is called a binding. Using (i.e. evaluating) an "unbound" symbol will raise an error.

# bind some symbols to values (aka create some bindings)
(def a 100)
(def b (+ 1 a)) # -> 101
(def c (+ b b)) # -> 202
(def d (- c 100)) # -> 102

# compile error: unknown symbol x
(def e (+ a x))

Bindings created with def have lexical scoping and are constant; they cannot be changed after definition. For bindings that can be altered, like variables in other programming languages, use the var special form. The assignment special form set can then be used to update the binding.

Note that the terms "def" and "var" will sometimes be used to refer to the created bindings themselves (i.e. the associations between symbols and values).

(def a 100)
a # -> 100

# compile error: cannot set constant
(set a 10)

a # -> 100

(var myvar 1)
myvar # -> 1

(set myvar 10)
myvar # -> 10

In the global scope, you can use the :private keyword in a def or var form to prevent the corresponding binding from being exported to code that imports your current module. You can also add documentation to a global function by passing a string to the def or var special forms.

Private bindings can also be created using the def- and var- macros. Use of the :private keyword may be more suited to the progammatic manipulation of code.

(def mydef :private "This will have private scope. My doc here." 123)
(var myvar "docstring here" 321)

(def- mydef2 "Private, the sequel" 124)
(var- myvar2 "We can do sequels too!" 322)

Scopes

Defs and vars (i.e. bindings) live inside what is called a scope. A scope is simply where the bindings are valid. If a binding is referenced outside of its scope, the compiler will throw an error. Scopes are useful for organizing your bindings and can provide flexibility in expressing your programs. There are two main ways to create a scope in Janet.

The first is to use the do special form. do executes a series of statements in a scope and evaluates to the last statement. Bindings created inside the form do not escape outside of its scope.

(def a :outera)

(do
  (def a 1)
  (def b 2)
  (+ a b)) # -> 3

a # -> :outera

# compile error: unknown symbol b
b

Any attempt to reference the bindings created within the do form after it has finished executing will fail.

Note that in the example above, defining a inside the do form did not overwrite the original definition of a for the global scope. As the code in the example demonstrates, the original definition of a was still accessible after the do form. We say that a or its binding was "shadowed" within the do form.

Another way to create a scope is to create a closure. The fn special form does this by introducing a scope just like the do special form.

(def a :first!)
a # -> :first!

(def f
  (fn []
    # a new scope has been introduced
    (def b a)
    # shadowing a's outer binding
    (def a :inner!)
    # after the following, the new scope "ends"
    @[b a]))

# binding created in f's scope are not valid here
a # -> :first!
# compile error: unknown symbol b
b

# f can access those bindings though
(f) # -> @[:first! :inner!]

# shadow a's original binding
(def a :second!)
a # -> :second!

# f's bindings unaffected by immediately previous shadowing
(f) # -> @[:first! :inner!]

It also possible to create new scopes using the if and while special forms. In both cases, bindings can be introduced in their respective "condition" portions and the bindings will be valid within the rest of each of the forms.

# using def in if's condition
(defn f [x]
  (if (def n x)
    n
    (when (not n) :was-nil!)))

(f :something) # -> :something

(f nil) # -> :was-nil!

# using def in while's condition
(def arr @[2 1 0])
(while (def n (length arr))
  (array/pop arr)
  (when (zero? n) (break)))

arr # -> @[]

There is also a built-in macro, let, that does multiple defs at once, and then introduces a scope. let is a wrapper around a combination of a do and defs, and is the most "functional" way of creating bindings.

(let [a 1
      b 2]
  (+ a b)) # -> 3

The above is equivalent to the earlier example using do and def.

Note that the special forms def and var do not introduce new scopes and the following sort of code is valid.

(def a (def x 1))

a # -> 1
x # -> 1

(var b (var y 2))

b # -> 2
y # -> 2