Language Reference
The type system, protocols, macros, and async/concurrency features are work in progress. This reference covers the stable core of the language.
Comments
# end-of-line comment
---
block comment
---
Literals
Booleans
true
false
Numbers
Integer size is inferred from the literal value and sign:
1_234_567 # u32
+1 # i8
-1 # i8
0xFF # u8
+0xFF # i8
0xFfFf # u16
0xFFFF_FFFF # u32
0xFFFF_FFFF_FFFF_FFFF # u64
0o_1234_5670 # octal
0b_0101_1111 # binary
Floats and decimals:
1.0 # f32
1.0e100_000 # f64
1.0d # decimal — cannot mix with floats
1.0d-100 # decimal with negative exponent
Strings
Single-quoted, with interpolation and multiline support:
'hello world'
'hello ${1 + 2}'
'
multiline
string
'
Escape sequences:
'\n' # newline
'\t' # tab
'\\' # backslash
'\'' # single quote
'\x0f' # hex code point
'\u{10_ff_ff}' # Unicode code point
'\${' # literal ${
String blocks — no need to escape single quotes, indentation stripped:
":
supports templating ${bar}
no need to escape 'spam'
Tagged template strings pass raw parts and values to a function:
fmt'hello ${name}'
sql'SELECT * FROM users WHERE name = ${name}'
rx'(?<group>[a-z]+)'
Raw strings — escape sequences are not processed:
raw'foo \n \t bar'
raw":
foo \n
bar
Tagged literals
Postfix function application for readable units:
10sec # == sec 10
10.5min # == min 10.5
(foo)min # == min foo
Collections
# sequences (tuple-optimized when used as such)
[]
[1, 2, 3]
seq 1, 2, 3
# records (compile-time field names)
{}
{foo: 1, bar: 2}
{foo: 1, 'ni na': 2, (key): 3} # computed keys
# dictionaries (runtime keys)
dict {foo: 1, 'bar': 2, (key): 3}
# sets
set 1, 2, 3
ordered_set 3, 2, 1
Operators
Arithmetic
-a # unary minus
a + b
a - b
a * b
a / b
a // b # integer divide
a ** b # power
a % b # remainder (sign follows dividend)
a %% b # true modulus (sign follows divisor)
a /% b # divmod — returns [quotient, remainder]
Logical
Operands must be bools, returns bool:
not a
a and b
a or b
a xor b
Bitwise
~a # not
a & b # and
a ^ b # xor
a >> b # shift right
a << b # shift left
a >>> b # rotate right
a <<< b # rotate left
Comparison
Chainable, always returns bool:
a == b
a != b
a > b
a >= b
a < b
a <= b
a > b > c # chained
a in b
a not in b
a >< b # disjoint
Ranges
0..10 # exclusive end
0...10 # inclusive end
'a'...'z' # char range
start..end
(1 + 2)..(3 + 4)
Spread
[head, ..tail]
[..seq1, ..seq2] # concat
{foo: bar, ..rest}
{..rec1, ..rec2} # merge
Bindings and Pattern Matching
Left-hand binding
foo = 1
[a, b] = [1, 2]
{x, y} = point
{x, y: z} = point # bind x, rename y to z
Patterns can include guards:
[x, y >= 2] = [1, 2]
[is_odd head, ..tail] = [3, 4, 5]
Rest patterns:
[head, ..tail] = [1, 2, 3, 4]
[head, ..middle, end] = [1, 2, 3, 4]
String patterns:
'start ${middle} end' = 'start foo end'
# middle == ' foo '
Record patterns match partially; sequence patterns match exactly:
{a} = {a: 1, b: 2} # ok — records are partial
[a, ..] = [1, 2] # ok — explicit rest discard
Right-hand binding
Capture the result of a multiline expression:
foo
arg1
arg2
|= result
match
match foo:
1: 'one'
2: 'two'
_: 'other'
Match on structure:
match foo:
[head, ..tail]: head
[]: 'empty'
Match with guards:
match foo:
n > 0 and n < 10: 'small positive ${n}'
n > 0: 'large positive ${n}'
even n: 'even number ${n}'
_: 'other'
Match on types:
match foo:
str s: 'its a string ${s}'
u8 n: 'its a u8 ${n}'
Match on sequence and record structure:
match items:
[]: 'empty'
[x]: 'one element'
[x, y]: 'two elements'
[x, ..rest]: 'head and rest'
match foobar:
{}: 'empty'
{foo: 1}: 'has foo = 1'
{foo: 1, ..rest}: 'has foo = 1 and more'
Functions
add = fn a, b:
result = a + b
result
# no args
greet = fn: 'hello'
# default args
greet = fn name='world': 'hello ${name}'
# pattern matching in args
foo = fn {x, y}: x + y
bar = fn [head, ..tail]: head
baz = fn arg, ..rest: arg
fn match sugar
classify = fn match n:
n > 0: 'positive'
n < 0: 'negative'
_: 'zero'
Mutual recursion
Forward references at module level allow mutual recursion without special syntax:
is_even = fn n:
match n:
0: true
_: is_odd n - 1
is_odd = fn n:
match n:
0: false
_: is_even n - 1
Application
Prefix application, right-to-left nesting:
log 'hello'
add 1, 2
# nested — right to left
foo bar spam ham
# == foo (bar (spam ham))
# multiline — indented args
add
mul 2, 3
mul 3, 4
Use ; as a strong inline separator (stronger than ,):
add mul 2, 3; mul 3, 4
# == add (mul 2, 3), (mul 3, 4)
Partial application with ?
? creates an anonymous function scoped to the current expression or pipe segment:
add5 = add 5, ?
add5 = ? + 5
filter is_divisible ?, 2 # == filter fn $: is_divisible $, 2
map ? * 2 # == map fn $: $ * 2
? is transparent through sequences, records, and operators — all ? in the same scope share one parameter:
[?, ?] # == fn $: [$, $]
{foo: ?, bar: ?} # == fn $: {foo: $, bar: $}
(...) is an explicit scope boundary:
foo (bar ?) # == foo (fn $: bar $)
Pipes
Left-to-right application:
'hello'
| capitalize
| log
# == log (capitalize 'hello')
Each pipe segment is its own ? scope:
1..10
| filter ? % 2 == 0
| map ? * 2
| [..?]
|= even_nums
Pass result as spread arguments:
[1, 2] | add ..?
# == fn [a, b]: add a, b
Error Handling
try unwraps Ok or propagates Err up the call stack:
fn foo:
a = try bar a
b = try baz a
Ok a + b
match handles errors explicitly:
fn foo:
match bar _:
Ok x: x + 1
Err e: log 'error: ${e}'
Error chaining:
fn foo:
match bar _:
Ok x: Ok x
Err e: Err e, 'foo failed'
Modules
{foo, bar} = import './foobar.fnk'
Types (work in progress)
# product types
Point = type: u8, u8
Circle = type: {x: u8, y: u8, r: u8}
# sum types / variants
Result = variant T, E:
Ok T
Err E
Shape = variant:
Circle {x: u8, y: u8, r: u8}
Rect {x: u8, y: u8, w: u8, h: u8}
Nil ()
# opaque types
UserId = type: ..u64
# generic types
Option = variant T:
Some T
None
Construction and matching:
circle = Circle {x: 1, y: 2, r: 5}
some = Some 42
match opt:
Some x: x
None: 0