Janet Notes and Examples

Janet Language Basics

Some quick notes on Janet here. For the full official story go straight to the Janet docs. See also JanetDocs which includes community-supplied examples.

# A comment.


The type of the object (as returned by the type function) is shown in the comment:

nil                  # :nil
true  false          # :boolean
0  6000  3_000_000   # :number
1.5  2e3  4e-2       # :number
:foo  :x-ray  :a/b   # :keyword
who?   me!  _  a/b   # :symbol

# More about this one later.
(fn [x] (* x x))     # :function

Typically, _ is used as a dummy variable.

Symbols evaluate to whatever they point/refer to. To stop them from being evaluated, you must 'quote-them.

Regarding the slashes in names of things: there’s no actual namespacing magic happening in Janet — the slashes are just part of the name (however, in practice, they’re used that way regardless (see the examples below, for example, math/sin and so on)).


As noted above, 5 and 5.2 are both just :numbers — there are no separate “int” and “float” types.

To remove the fractional part from a decimal number:

(math/round 5.8)  # => 6
(math/trunc 5.8)  # => 5


"foo bar"
(type "foo bar")  # => :string

# Newlines get eaten if you span multiple lines, but
# whitespace is otherwise preserved.
"this one is written
    over multiple
lines" # => "this one is written    over multiplelines"

# So-called longs-trings are delimited with one or more matching
# backticks. Often used for docstrings. Backslash-escapes aren't
# recognized in long-strings (though the newlines you type make
# it into the resulting string).
``a\tmulti-line # Can't put a raw tab in there!
string here``   # => "a\\tmulti-line\nstring here"

(string "con" "cat" "enate")   # => "concatenate"

(string/repeat "*" 5)          # => "*****"

# To get a substring:
(string/slice "abcdefgh" 2 5)  # => "cde"
# To find a substring:
(string/find "de" "abcdefgh")  # => 3

# String formatting.
(def s (string/format "see %p and %p" x y))
(printf "x: %p" x) # Useful for debugging.
(pp x)             # Or this if you don't need the symbol name.

See the string standard library for more (splitting, replacement, etc.)

Note that long-string docstrings are treated special: common leading whitespace is trimmed (lines are un-indented) so you don’t need to left-align all your docstrings at column 0.

See also the full list of format specifiers for string/format and friends.

Types of things in Janet

Objects can be recursive (like data structures) or non-recursive (like booleans, numbers, keywords, symbols, strings, and buffers).

Data structures include tuples & arrays, and structs & tables.

Strings and data structures in Janet come in two varieties: mutable and immutable. Mutables are prefixed with “@”. Summarized:

# Both are "bytes" (see the `bytes?` function):
 "foo"  # string (immutable)
@"foo"  # buffer (mutable)

# Both are "indexed" (see the `indexed?` function):
 [1 2]  # tuple (immutable)
@[1 2]  # array (mutable)

# Both are "dictionary" (see the `dictionary?` function):
 {:a 1 :b 2}  # struct (immutable)
@{:a 1 :b 2}  # table  (mutable)

The bytes? function returns true if its arg is either a string, buffer, keyword, or symbol (they are all byte sequences).

Both indexed and bytes objects are sequential and can be slice’d (you can take sub-arrays or sub-strings of them). See more about slicing in the arrays chapter.

Summarizing some of those:

  • Symbols, keywords, strings, and buffers are all byte sequences (8-bit clean).
  • Symbols, keywords, and strings are all immutable.
  • describe gets you a human-readable string description (so, a byte sequence) of a value. If the value is a recursive data structure, the string you get from describe (which, being a byte sequence, is non-recursive of course) contains a unique id (pointer) for that value.
  • Like other literals, strings and keywords evaluate to themselves.
  • Symbols, however, evaluate to whatever they’re bound to (unless you 'quote them).
  • Of course, when a symbol is bound to a string, the symbol and the string it refers to are both byte sequences.

There’s no literal for sets — use a set library like janet-set (note that it just gives you a thin interface over tables or structs).

You can get the type of any object via (type the-obj), which returns a keyword (see top of this file).

Types come with their own like-named functions for creating a new object of that type:

  • symbol, keyword, string, buffer. These first (if necessary) convert their args to byte sequences using describe, then they concat their args together if more than one.
  • tuple, array, struct, table

Note that:

  • Structs and tables (but also tuples, arrays, strings, buffers, keywords, and symbols) are associative. Using a key you can get a value from them using in or get.

  • Indexed (arrays and tuples) and bytes (strings, buffers, keywords, and symbols) are sequential. You can slice them (take sub-sequences of them), and can index into them with in or get.

Predicates for checking types






And there is also:

bytes?        (string, buffer, keyword, or symbol)
indexed?      (tuple or array)
dictionary?   (stuct or table)

idempotent?   (nil, boolean, number, symbol, keyword, string, function)

There is no associative? predicate function.
There is no sequential? (or sliceable?) predicate function.

Regarding idempotent?, it returns true if passed an “atomic” type. That is, anything other than:

  • buffer
  • tuple, array
  • struct, table

The “atomic” types evaluate to themselves when compiled.

See (doc "?") for a few more predicates.

Truthiness and Falsiness

Only nil and false are falsey. Everything else is truthy.

Defining Things

(def x 1)  # `x` must always refer to the value 1.
(var y 2)  # You can change to what `y` refers.

# You can't make `z` refer to anything else. The object to
# which `z` refers happens to be mutable.
(def z @[1 2])

Defs and vars are collectively known as “bindings” (a value bound to a symbol).

Changing/Setting Things

(var x 3.0)
(++ x)          # Shorthand for `(set x (+ x 1  ))`.
(+= x 5.0)      # Shorthand for `(set x (+ x 5.0))`.
(set x 99.9)

Note: Unlike (++ x), (inc x) doesn’t change the value of x.

See also the section on arrays and the one on tables.

Changing Between Types of Things

Convert (parse) string to number:

(scan-number "123")      # => 123
(scan-number "1.2e+03")  # => 1200
(scan-number "0xff")     # => 256

To convert between strings, arrays, tables, etc., see the chapters here for each kind of data structure, as well as more working with data structures.

Remember, Janet only has :number — not ints and floats — so there are no functions for converting between ints/floats (though there is the built-in int module).

Since only nil and false are falsey, there’s no coercion to a boolean — use predicate functions instead (like true?, false?, empty?, zero?, even?, etc.).


The rule is:

  • Immutables are compared by comparing their values, while
  • mutables are compared by pointer (identity).

Some examples:

# All of these comparisons (of immutables) evaluate to `true`:
(= nil nil)        # nil
(= false false)    # boolean
(= 1 1)            # number
(= :a :a)          # keyword
(= 'a 'a)          # symbol
(= "a" "a")        # string
(= [1 2] [1 2])    # tuple
(= {:a 1} {:a 1})  # struct

# ... and all of these comparisons (of mutables) evaluate to `false`:
(= @"a" @"a")        # buffer
(= @[1 2] @[1 2])    # array
(= @{:a 1} @{:a 1})  # table

Use deep= for deep comparison of mutables.


You can see the unique “id” of a recursive object by calling (describe foo) on the object. (See below for pretty-printing.)

Control Structures

do provides a new local scope and lets you execute a series of possibly side-effecting expressions within it. It evaluates to its last value.

let, when, function bodies, while, for, loop, and other constructs provide an “implicit-do”. (In fact, let is just shorthand for a do with defs inside it.). Other constructs, including if, cond, case, seq, and function calls, are expressions that evaluate to a particular value.

(if expr

(while stays-true
  (break)  # Can `break` out of a while loop.

for   # C-style looping
loop  # general looping
seq   # "list comprehension"

For looping, see the chapter on looping.

Note: there’s no when-not — use (when (not ...)) or unless.

Functional vs side-effects

Here are two incomplete lists of side-effecty (“implicit-do”) vs functional constructs/functions:

Functional (expressions)
and, or, map, filter, reduce, reduce2, cond, case, comp
do, when, unless, when-let, with, while, for, loop, each, eachk, eachp

Some constructs roughly appear both in side-effecty and functional forms:

Side-effecty Functional
loop seq
when, unless if, if-not
printf string/format
reverse! reverse
sort sorted
sort-by sorted-by
buffer/blit string/replace
merge-into merge

Another example:

(repeat 4 (print "foo")       # side-effecty (does the thing 4 times)
(seq [_ :range [0 4]] "foo")  # functional

Note: there’s no “reversed” — just use reverse.

More about reversing: note that string/reverse and reverse both are functional-style and return a new object.

BTW, don’t confuse repeat with string/repeat, ex:

(string/repeat "x" 3)  # => "xxx"


(fn         [x] (* x 2))  # Anonymous function.
(fn doubler [x] (* x 2))  # Not-anonymous function.

# Garden-variety function definiton.
(defn doubler
  ``Docstring goes here
  thank you very much.``
  (* x 2))

To call functions in the standard library, you don’t need to import anything; just go ahead and call them, ex., (array/concat ...).

A “cfunction” is one that’s been implemented in C. Some built-in Janet functions are actually cfunctions (for example, see (doc os/time)). For more info on these, see Writing C Functions.

For more about functions, see the functions chapter.

Pretty-Printing & Formatting


(print x) prints x
(printf "%j" x) prints in jdn format
(printf "%M" x) prints nicely-formatted
(pp x) pretty-prints x, configurable

Formatting Strings:

(describe x) human-readable byte sequence
(string/format ...) see printf above
(buffer/format ...) same, but for buffers

For serializing, see marshal and unmarshal.

Top-Level Functions

Janet comes with a luxurious selection of rich corinthian top-level functions.

Possibly todo

Either in this file, or elsewhere:

  • prompt and label
  • dynamic bindings
  • Environments & Contexts
  • Fibers
  • Macros