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.

Literals

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.

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, they’re used that way regardless (see the examples below, for example, math/sin and so on)).

Numbers

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

Strings

"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:

# 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.

Both indexed and bytes objects 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.
  • 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 constructor functions:

  • symbol, keyword, string, buffer. These 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.

Structs, tables, tuples, arrays, strings, buffers, keywords and symbols are all associative data structures.

Indexed (arrays and tuples) and bytes (strings, buffers, keywords, and symbols) are sequential, though I don’t think there’s an official “sequential” interface for that.

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”.

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

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’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).

Comparison

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.

Id

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”. (In fact, let is just shorthand for a do with defs inside it.)

(if expr
  this
  otherwise-this)

(while stays-true this that other)  # Can `break` out.

cond
case
when
for   # C-style looping
loop  # general looping
seq   # list comprehension

For looping, see the chapter on looping.

Functions

(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]
  (* 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 ...).

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

For more about functions, see the functions chapter.

Pretty-Printing & Formatting

Printing:

(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

  • dynamic bindings
  • Environments & Contexts
  • Fibers
  • Macros