Language reference
This is a living specification of the current set of built-ins in the .lurk
package.
Built-in atoms
nil
nil
is boolean false and can also represent the empty list.
It has its own type differently from other symbols.
lurk-user> (eq nil '())
[3 iterations] => t
lurk-user> (cdr '(1))
[2 iterations] => nil
lurk-user> (if nil "true" "false")
[3 iterations] => "false"
t
t
is boolean true. In boolean contexts like if
, anything that isn't nil
is considered false, but t
is generally used.
lurk-user> (eq 1 1)
[2 iterations] => t
lurk-user> (if t "true" "false")
[3 iterations] => "true"
Built-in operators
atom
(atom x)
returns nil
if x
is a pair, and t
otherwise.
lurk-user> (atom nil)
[2 iterations] => t
lurk-user> (atom t)
[2 iterations] => t
lurk-user> (atom (cons 1 2))
[4 iterations] => nil
lurk-user> (atom '(1 2 3))
[2 iterations] => nil
apply
(apply f args)
will call f
with the argument list args
and return its result. args
must be a list.
lurk-user> (apply (lambda (x y z) (+ x (+ y z))) '(1 2 3))
[11 iterations] => 6
lurk-user> (apply (lambda (x y z) (+ x (+ y z))) (list 1 2 3))
[11 iterations] => 6
lurk-user> ((apply (lambda (x y z) (+ x (+ y z))) (list 1 2)) 3) ;; partial application also works
[12 iterations] => 6
lurk-user> (apply (lambda (x) x) 1)
[3 iterations] => <Err ArgsNotList>
begin
(begin e1 e2 ... en)
evaluates e1
, e2
, ..., en
and returns the reduced version of en
.
It is particularly useful when we want to manifest side-effects when evaluating the inner expressions e1
, e2
, ...
lurk-user> (begin 1 2 3)
[4 iterations] => 3
lurk-user> ((lambda (x) (begin (emit x) (+ x x))) 1)
1
[7 iterations] => 2
bignum
(bignum x)
tries to convert x
to a big num. Can only be used with commitments currently. Returns <Err CantCastToBigNum>
if the types are incompatible.
lurk-user> (bignum (commit 1))
[3 iterations] => #0xf99d96623838468091ce6590ccb3b829938823d964f3f5962f837f1400e2b
car
(car cons-cell)
returns the first element of its argument if it is a pair. Also works with strings, returning its first character. Returns <Err NotCons>
otherwise.
lurk-user> (car (cons 1 2))
[4 iterations] => 1
lurk-user> (car '(1 2 3))
[2 iterations] => 1
lurk-user> (car (strcons 'a' "b"))
[4 iterations] => 'a'
lurk-user> (car "abc")
[2 iterations] => 'a'
cdr
(cdr cons-cell)
returns the second element of its argument if it is a pair. Also works with strings, returning its tail. Returns <Err NotCons>
otherwise.
lurk-user> (cdr (cons 1 2))
[4 iterations] => 2
lurk-user> (cdr '(1 2 3))
[2 iterations] => (2 3)
lurk-user> (cdr (strcons 'a' "b"))
[4 iterations] => "b"
lurk-user> (cdr "ab")
[2 iterations] => "b"
char
(char x)
tries to convert x
to a character. Can be used with integers. Returns <Err CantCastToChar>
if the types are incompatible.
A char can store 32-bits of data, and this conversion truncates 64-bit integers into its 32 least significant bits.
lurk-user> (char 0x41)
[2 iterations] => 'A'
lurk-user> (char 65)
[2 iterations] => 'A'
lurk-user> (char nil)
[2 iterations] => <Err CantCastToChar>
commit
(commit x)
is equivalent to (hide #0x0 x)
. It creates a commitment to the result of evaluating x
, but sets the secret to the default of zero.
lurk-user> (commit 1)
[2 iterations] => #c0xf99d96623838468091ce6590ccb3b829938823d964f3f5962f837f1400e2b
lurk-user> (open #c0xf99d96623838468091ce6590ccb3b829938823d964f3f5962f837f1400e2b)
[3 iterations] => 1
lurk-user> (secret #c0xf99d96623838468091ce6590ccb3b829938823d964f3f5962f837f1400e2b)
[3 iterations] => #0x0
comm
(comm x)
tries to convert x
to a commitment. Can only be used with big nums currently. Returns <Err CantCastToComm>
if the types are incompatible.
lurk-user> (comm #0x0)
[2 iterations] => #c0x0
lurk-user> (bignum (comm #0x0))
[3 iterations] => #0x0
cons
(cons x y)
creates a new pair with first element x
and second element y
. The result of (cons x y)
is denoted by (x . y)
or simply (x)
if y
is nil.
lurk-user> (eq (cons 1 nil) (cons 1 '()))
[6 iterations] => t
lurk-user> (cons 1 2)
[3 iterations] => (1 . 2)
lurk-user> (car (cons 1 2))
[4 iterations] => 1
lurk-user> (cdr (cons 1 2))
[4 iterations] => 2
lurk-user> (cons 1 '(2 3))
[3 iterations] => (1 2 3)
list
(list e1 e2 ... en)
creates a list with the reduced versions of e1
, e2
, ..., en
.
lurk-user> (list)
[1 iteration] => nil
lurk-user> (list (+ 1 1) "hi")
[4 iterations] => (2 "hi")
current-env
(current-env)
returns the current environment. The current environment can be modified by let
, letrec
and lambda
expressions.
See also the REPL meta-commands def
, defrec
and clear
for interacting with the current REPL environment.
lurk-user> (current-env)
[1 iteration] => <Env ()>
lurk-user> (let ((x 1)) (current-env))
[3 iterations] => <Env ((x . 1))>
lurk-user> (letrec ((x 1)) (current-env))
[4 iterations] => <Env ((x . <Thunk 1>))>
lurk-user> ((lambda (x) (current-env)) 1)
[4 iterations] => <Env ((x . 1))>
emit
(emit x)
prints x
to the output and returns x
.
lurk-user> (emit 1)
1
[2 iterations] => 1
lurk-user> (let ((f (lambda (x) (emit x)))) (begin (f 1) (f 2) (f 3)))
1
2
3
[16 iterations] => 3
empty-env
(empty-env)
returns the canonical empty environment.
lurk-user> (empty-env)
[1 iteration] => <Env ()>
lurk-user> (eq (empty-env) (current-env))
[3 iterations] => t
lurk-user> (let ((x 1)) (eq (empty-env) (current-env)))
[5 iterations] => nil
eval
(eval form env)
evaluates form
using env
as its environment. (eval form)
is equivalent to (eval form (empty-env))
. Here form
must be a quoted syntax tree of Lurk code.
lurk-user> (eval 1)
[3 iterations] => 1
lurk-user> (eval (+ 1 2)) ;; this expands to `(eval 3)` -- probably not what was intended!
[5 iterations] => 3
lurk-user> (eval '(+ 1 2)) ;; this will actually call `eval` with `'(+ 1 2)` as its argument
[5 iterations] => 3
lurk-user> (eval (cons '+ (cons 1 (cons 2 nil))))
[11 iterations] => 3
lurk-user> (cons '+ (cons 1 (cons 2 nil)))
[7 iterations] => (+ 1 2)
lurk-user> (eval 'x)
[3 iterations] => <Err UnboundVar>
lurk-user> (eval 'x (let ((x 1)) (current-env)))
[6 iterations] => 1
eq
(eq x y)
returns t
if x
and y
, after both being reduced, are equal and nil
otherwise.
lurk-user> (eq 1 (- 2 1))
[4 iterations] => t
lurk-user> (eq nil nil)
[2 iterations] => t
lurk-user> (eq 'a' 'b')
[3 iterations] => nil
lurk-user> (eq (+ 1 2) (+ 2 1))
[5 iterations] => t
lurk-user> (eq 'a' (char (+ (u64 'A') 32)))
[7 iterations] => t
lurk-user> (eq (cons 1 2) (cons 2 1))
[7 iterations] => nil
eqq
(eqq x y)
returns t
if x
and y
, after only reducing y
, are equal and nil
otherwise.
Note: the second "q" in eqq
is a reference to "quote" because (eqq x y)
is equivalent to (eq (quote x) y)
.
lurk-user> (eqq (1 . 2) (cons 1 2))
[4 iterations] => t
type-eq
(type-eq x y)
returns t
if x
and y
, after both being reduced, have the same type and nil
otherwise.
lurk-user> (type-eq 1 2)
[3 iterations] => t
lurk-user> (type-eq (cons 1 2) (cons 3 4))
[7 iterations] => t
lurk-user> (type-eq 'x 'y)
[3 iterations] => t
lurk-user> (type-eq 'x t)
[3 iterations] => nil
lurk-user> (type-eq 'x nil)
[3 iterations] => nil
lurk-user> (type-eq t nil)
[3 iterations] => nil
lurk-user> (type-eq '() '(1 2)) ;; this is surprisingly the case because `'()` is `nil`, which is a different type from `cons`
[3 iterations] => nil
type-eqq
(type-eqq x y)
returns t
if x
and y
, after only reducing y
, have the same type and nil
otherwise.
Note: the second "q" in type-eqq
is a reference to "quote" because (type-eqq x y)
is equivalent to (type-eq (quote x) y)
.
lurk-user> (type-eqq 1 (+ 1 2))
[4 iterations] => t
lurk-user> (type-eqq (+ 1 2) 1)
[2 iterations] => nil
lurk-user> (type-eqq (+ 1 2) (cons 1 2))
[4 iterations] => t
hide
(hide secret value)
creates a hiding commitment to value
with secret
as the salt. secret
must be a big num.
lurk-user> (hide #0x123 456)
[3 iterations] => #c0x4420657169325d52f910f0ffe606b3a7b600a982691926b207f21350120d3d
lurk-user> (open #c0x4420657169325d52f910f0ffe606b3a7b600a982691926b207f21350120d3d)
[3 iterations] => 456
lurk-user> (secret #c0x4420657169325d52f910f0ffe606b3a7b600a982691926b207f21350120d3d)
[3 iterations] => #0x123
if
(if cond then else)
returns then
if cond
is non-nil or else
otherwise. Only nil
values are considered false. (if cond then)
is equivalent to (if cond then nil)
lurk-user> (if t "true" "false")
[3 iterations] => "true"
lurk-user> (if nil "true" "false")
[3 iterations] => "false"
lurk-user> (if nil "true")
[2 iterations] => nil
lurk-user> (if 0 "true") ;; note: `0` is *not* `nil`
[3 iterations] => "true"
lambda
(lambda (args...) body)
creates a function that takes a list of arguments and returns the result of evaluating body
by binding the variable bindings to the supplied arguments. A function is called when it is in the head of a list.
The list of arguments can optionally end with &rest <var>
, which denotes that any remaining arguments, if present, are bound to <var>
as a list.
lurk-user> (lambda () 1)
[1 iteration] => <Fun () (1)>
lurk-user> (lambda (x) x)
[1 iteration] => <Fun (x) (x)>
lurk-user> ((lambda () 1))
[3 iterations] => 1
lurk-user> ((lambda (x) x) 1)
[4 iterations] => 1
lurk-user> ((lambda (x y z) (+ x (+ y z))) 1 2 3)
[10 iterations] => 6
lurk-user> (let ((f (lambda (x y z) (+ x (+ y z))))) (+ (f 1 2 3) (f 3 2 1))) ;; here `f` is the variable bound to the `lambda`
[19 iterations] => 12
lurk-user> ((lambda (x y &rest z) z) 1 2 3)
[6 iterations] => (3)
lurk-user> ((lambda (x y &rest z) z) 1 2)
[5 iterations] => nil
lurk-user> ((lambda (&rest x) x))
[3 iterations] => nil
Evaluating the body happens as if there were a begin
surrounding it.
lurk-user> ((lambda () (emit 1) (emit 2)))
1
2
[6 iterations] => 2
let
(let ((var binding) ...) body)
extends the current environment with a set of variable bindings and then evaluates body
in the updated environment.
let
is used for binding values to names and modifying environments.
See also the def
REPL meta command.
lurk-user> (let ((x 1) (y 2)) (+ x y))
[6 iterations] => 3
lurk-user> (let ((x 1) (y 2)) (current-env))
[4 iterations] => <Env ((y . 2) (x . 1))>
Similarly to lambda
, the body is interpreted as if there were a begin
surrounding it.
lurk-user> (let ((a 1) (b 2)) (emit a) (emit b))
1
2
[7 iterations] => 2
letrec
(letrec ((var binding) ...) body)
is similar to let
, but it enables mutually recursive bindings.
See also the defrec
REPL meta command.
lurk-user> (letrec ((x 1)) x)
[4 iterations] => 1
lurk-user> (letrec ((x 1)) (current-env))
[4 iterations] => <Env ((x . <Thunk 1>))> ;; Thunks are the internal representation used for recursive evaluation
lurk-user>
(letrec ((last (lambda (x) (if (cdr x) (last (cdr x)) (car x)))))
(last '(1 2 3)))
[20 iterations] => 3
lurk-user>
(letrec ((odd? (lambda (n) (if (= n 0) nil (even? (- n 1)))))
(even? (lambda (n) (if (= n 0) t (odd? (- n 1))))))
(odd? 5))
[53 iterations] => t
Similarly to let
, the body is interpreted as if there were a begin
surrounding it.
lurk-user> (letrec ((a 1) (b 2)) (emit a) (emit b))
1
2
[9 iterations] => 2
u64
(u64 x)
tries to convert x
to a 64-bit unsigned integer. Can be used with characters. Returns <Err CantCastToU64>
if the types are incompatible.
lurk-user> (u64 'A')
[2 iterations] => 65
lurk-user> (u64 100) ;; this is a no-op
[2 iterations] => 100
lurk-user> (u64 nil)
[2 iterations] => <Err CantCastToU64>
open
(open x)
opens the commitment x
and return its value. If y
is a big num, then (open y)
is equivalent to (open (comm y))
.
lurk-user> (open (commit 1))
[3 iterations] => 1
lurk-user> (open #c0xf99d96623838468091ce6590ccb3b829938823d964f3f5962f837f1400e2b)
[2 iterations] => 1
lurk-user> (open #0xf99d96623838468091ce6590ccb3b829938823d964f3f5962f837f1400e2b)
[2 iterations] => 1
quote
(quote x)
returns x
as its un-evaluated syntax form. (quote x)
is equivalent to 'x
.
lurk-user> (quote 1)
[1 iteration] => 1
lurk-user> (quote (+ 1 2))
[1 iteration] => (+ 1 2)
lurk-user> (quote 1)
[1 iteration] => 1
lurk-user> (quote (+ 1 2))
[1 iteration] => (+ 1 2)
lurk-user> '(+ 1 2)
[1 iteration] => (+ 1 2)
lurk-user> (cdr (quote (+ 1 2)))
[2 iterations] => (1 2)
lurk-user> (eval (cdr '(- + 1 2)))
[6 iterations] => 3
secret
(secret x)
opens the commitment x
and return its secret. If y
is a big num, then (secret y)
is equivalent to (secret (comm y))
.
lurk-user> (secret (commit 1))
[3 iterations] => #0x0
lurk-user> (secret (hide #0x123 2))
[4 iterations] => #0x123
lurk-user> (secret #c0x76cc74360d9492188f072ecdafd2ddf50fadac0ce2007c551c09fdcd556109)
[2 iterations] => #0x123
lurk-user> (secret #0x76cc74360d9492188f072ecdafd2ddf50fadac0ce2007c551c09fdcd556109)
[2 iterations] => #0x123
strcons
(strcons x y)
creates a new string with first element x
and rest y
. x
must be a character and y
must be a string. Strings are represented as lists of characters, but because the type of strings and lists are different, strcons
is used for constructing a string instead.
lurk-user> (strcons 'a' nil) ;; the empty list is not the same as the empty string
[3 iterations] => <Err NotString>
lurk-user> (strcons 'a' "")
[3 iterations] => "a"
lurk-user> (strcons 'a' "bc")
[3 iterations] => "abc"
lurk-user> (car (strcons 'a' "bc"))
[4 iterations] => 'a'
lurk-user> (cdr (strcons 'a' "bc"))
[4 iterations] => "bc"
lurk-user> (cons 'a' "bc")
[3 iterations] => ('a' . "bc") ;; note how the cons is not the same thing as "abc"
breakpoint
(breakpoint)
places a debugger breakpoint at the current evaluation state and returns nil
.
(breakpoint x)
places a debugger breakpoint and returns x
, reduced.
!(help debug)
for more information on the native debugger.
fail
(fail)
errors out from evaluation, unprovably.
Built-in numeric operators
+
(+ a b)
returns the sum of a
and b
. Overflow can happen implicitly. Returns <Err InvalidArg>
if the types are not compatible.
lurk-user> (+ 1 2)
[3 iterations] => 3
lurk-user> (+ 1n 2n)
[3 iterations] => 3n
lurk-user> (+ #0x1 #0x2) ;; no big num arithmetic yet
[3 iterations] => <Err InvalidArg>
lurk-user> (+ 18446744073709551615 18446744073709551615)
[2 iterations] => 18446744073709551614 ;; implicit overflow for u64
-
(- a b)
returns the difference between a
and b
. Underflow can happen implicitly. Returns <Err InvalidArg>
if the types are not compatible.
lurk-user> (- 2 1)
[3 iterations] => 1
lurk-user> (- 2n 1n)
[3 iterations] => 1n
lurk-user> (- 0 1)
[3 iterations] => 18446744073709551615
lurk-user> (- 0n 1n)
[3 iterations] => 2013265920n
*
(* a b)
returns the product of a
and b
. Overflow can happen implicitly. Returns <Err InvalidArg>
if the types are not compatible.
lurk-user> (* 2 3)
[3 iterations] => 6
lurk-user> (* 2n 3n)
[3 iterations] => 6n
lurk-user> (* 18446744073709551615 18446744073709551615)
[2 iterations] => 1
/
(/ a b)
returns the quotient between a
and b
. Returns <Err InvalidArg>
if the types are not compatible. Returns <Err DivByZero>
if b
is zero.
When a
and b
are integers, the fractional part of the result is truncated and the result is the usual integer division.
When a
and b
are native field elements, (/ a b)
returns the field element c
such that (eq a (* c b))
. In other words, (/ 1n x)
gives the multiplicative inverse of the native field element x
.
lurk-user> (/ 10 2)
[3 iterations] => 5
lurk-user> (/ 9 2)
[3 iterations] => 4
lurk-user> (/ 10n 2n)
[3 iterations] => 5n
lurk-user> (/ 9n 2n)
[3 iterations] => 1006632965n
lurk-user> (* 1006632965n 2n)
[3 iterations] => 9n
lurk-user> (* (/ 9 2) 2)
[4 iterations] => 8
lurk-user> (/ 1 0)
[3 iterations] => <Err DivByZero>
%
(% a b)
returns the remainder of dividing a
by b
. Returns <Err NotU64>
if the arguments are not unsigned integers.
lurk-user> (% 15 7)
[3 iterations] => 1
lurk-user> (% 15 6)
[3 iterations] => 3
lurk-user> (/ 15 6)
[3 iterations] => 2
lurk-user> (+ (* 2 6) 3)
[5 iterations] => 15
=
(= a b)
returns t
if a
and b
are equal and nil
otherwise. The arguments must be numeric. Returns <Err InvalidArg>
if the types are not compatible.
lurk-user> (= 123 123)
[2 iterations] => t
lurk-user> (= 123n 123n)
[2 iterations] => t
lurk-user> (= #0x123 #0x123)
[2 iterations] => t
lurk-user> (= "abc" "abc")
[2 iterations] => <Err InvalidArg>
<
(< a b)
returns t
if a
is strictly less than b
and nil
otherwise. The arguments must be numeric. Returns <Err InvalidArg>
if the types are not compatible. Note that native field elements cannot be compared like this.
lurk-user> (< "a" "b")
[3 iterations] => <Err InvalidArg>
lurk-user> (< 1n 2n)
[3 iterations] => <Err NotU64>
lurk-user> (< #0x123 #0x456)
[3 iterations] => t
lurk-user> (< 123 456)
[3 iterations] => t
>
(> a b)
returns t
if a
is strictly greater than b
and nil
otherwise. The arguments must be numeric. Returns <Err InvalidArg>
if the types are not compatible. Note that native field elements cannot be compared like this.
lurk-user> (> #0x123 #0x456)
[3 iterations] => nil
lurk-user> (> 456 123)
[3 iterations] => t
<=
(<= a b)
returns t
if a
is less than or equal to b
and nil
otherwise. The arguments must be numeric. Returns <Err InvalidArg>
if the types are not compatible. Note that native field elements cannot be compared like this.
lurk-user> (<= #0x123 #0x456)
[3 iterations] => t
lurk-user> (<= 123 123)
[2 iterations] => t
>=
(>= a b)
returns t
if a
is greater than or equal to b
and nil
otherwise. The arguments must be numeric. Returns <Err InvalidArg>
if the types are not compatible. Note that native field elements cannot be compared like this.
lurk-user> (>= #0x123 #0x456)
[3 iterations] => nil
lurk-user> (>= 123 123)
[2 iterations] => t