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.
Literals
The type (as returned by the type
function) is shown in the comment:
# :nil
nil # :boolean
true false 0 6000 3_000_000 # :number
1.5 2e3 4e-2 # :number
-ray :a/b # :keyword
:foo :x! _ a/b # :symbol who? me
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.
Numbers
As noted above, 5 and 5.2 are both just :number
s — there’s no separate “int” and “float” types like in Python.
To remove the fractional part from a decimal number:
/round 5.8) #=> 6
(math/trunc 5.8) #=> 5 (math
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.
-line
``a multi
string here``
"con" "cat" "enate") #=> "concatenate"
(string
# To get a substring:
/slice "abcdefgh" 2 5) #=> "cde"
(string# To find a substring:
/find "de" "abcdefgh") #=> 3 (string
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):
1 :b 2} # struct (immutable)
{:a @{: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 usingdescribe
, 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
(2) # You can change to what `y` points.
(var y
# 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
3.0)
(var x
(++ x)5.0)
(+= x set x 99.9) (
See also the section on arrays and the one on tables.
Changing Types of Things
# String to number
-number "123") #=> 123 (scan
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).
Comparison
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.
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”.
if expr this else-that)
(while stays-true this that other) # Can `break` out.
(
cond
case
whenfor # C-style looping
# general looping
loop # list comprehension seq
For looping, see the chapter on looping.
Functions
* x 2)) # Anonymous function.
(fn [x] (* x 2)) # Not-anonymous function.
(fn doubler [x] (
# Garden variety function definiton.
(defn doubler
``Docstring goes here
thank you very much.``
[x]* 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& c] # Extra args passed in are put into the `c` tuple.
[a b
...)
# optional args
(defn foo&opt c d] # `c` and `d` are optional (`nil` if not supplied)
[a b 42) # Give `c` a default value.
(default c
...)
# Ignore the rest of the args.
(defn foo&] # Must pass at least two args.
[a b
...)
# keyword-style args
(defn foo&keys {:size size
[
:color color
:style style}]
(pp size)
(pp color)
(pp style)
...)# and call it like
8 :color "red" :style "retro")
(foo :size
# 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:
8 :color "red" :style "retro"}) (foo {:size
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
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
- splice
;
- dynamic bindings
- Environments & Contexts
- Fibers
- Macros