Janet Notes and Examples

Janet Language Basics

Janet in 2 minutes, at Mach 1, from 3 km up. For the full official story, go straight to the Janet docs. See also JanetDocs which includes community-supplied examples.

# A comment.


The type (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

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.

Note that slashes in symbols and keywords are just part of the name — there’s no namespace magic happening.


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

To remove the fractional part from a decimal number:

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


"foo bar"

# So-called longs-trings. Delimited with one or more matching backticks.
# Often used for docstrings. Backslash-escapes don't work in long-strings.
``a multi-line
string here``

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

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

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

Types of things in Janet

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

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

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

# 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 tells you if its arg is either a string, buffer, keyword, or symbol.

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 back (which is non-recursive, of course) contains a unique id (pointer) for that value.
  • Strings and keywords evaluate to themselves.
  • Symbols evaluate to whatever they’re bound to.
  • Of course, when a symbol is bound to a string, the symbol and the string it points 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) (returns a keyword — see top of this file).

Types come with their own like-named constructor functions:

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

Note that although symbols, keywords, strings, and buffers are all themselves bytes, symbols usually refer to (and evaluate to) other objects.

Truthiness and Falsiness

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

Defining Things

(def x 1)  # 'x must always point to 1
(var y 2)  # You can change to what `y` points.

# You can't make `z` point to anything else, but the object it points
# to here happens to be mutable.
(def z @[1 2])

Changing/Setting Things

(var x 3.0)
(++ x)
(+= x 5.0)
(set x 99.9)

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

Changing Types of Things

# String to number
(scan-number "123")  #=> 123

To convert between strings, arrays, tables, etc., see each data structure’s chapter.

Remember, Janet only has :number — not ints and floats, so there’s 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?, even?, and so on).


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

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

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

See deep= for deep comparison of mutables.


You can see the unique “id” of a recursive object by calling (string 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, and function bodies provide an “implicit-do”.

(if expr this else-that)
(while stays-true this that other) # Can `break` out.
for   # C-style looping
loop  # general looping
seq   # list comprehension

For looping, see the chapter on looping.


(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)

# Can define functions inside of other functions, and can
# use `def`'s in general inside of functions, if you like.

# variadic
(defn foo
  [a b & c]  # Extra args passed in are put into the `c` tuple.

# optional args
(defn foo
  [a b &opt c d]  # `c` and `d` are optional (`nil` if not supplied)
  (default c 42)  # Give `c` a default value.

# Ignore the rest of the args.
(defn foo
  [a b &]  # Must pass at least two args.

# keyword-style args
(defn foo
  [&keys {:size size
          :color color
          :style style}]
  (pp size)
  (pp color)
  (pp style)
# and call it like
(foo :size 8 :color "red" :style "retro")

# Note, you *could* skip using `&keys` and only use destructuring by itself:
(defn foo
  [{:size size
    :color color
    :style style}]
  (pp size)
  (pp color)
  (pp style)
# But then calling would look like:
(foo {:size 8 :color "red" :style "retro"})

Also supported:

  • destructuring
  • defn- for not-to-be-exported functions

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

Note that “cfunction”s are just functions Janet uses which are implemented in C (for an example, see (doc os/time)).

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

  • splice ;
  • dynamic bindings
  • Environments & Contexts
  • Fibers
  • Macros