Data types
Even though Lurk is not statically typed, it is strongly typed.
Unsigned integers
The default numeric type in Lurk is u64
, which roundtrips as one would expect:
lurk-user> (- 0 1)
[3 iterations] => 18446744073709551615
lurk-user> (+ 18446744073709551615 1)
[3 iterations] => 0
Finite field elements
Lurk also allows operating over finite field elements directly, which is cheaper than dealing with u64
in terms of proving costs.
lurk-user> (+ 1n 2n)
[3 iterations] => 3n
Beware, though, that different finite fields have different sizes. The current Lurk instantiation uses Baby Bear.
lurk-user> (- 0n 1n)
[3 iterations] => 2013265920n
So, to write stable Lurk code, use finite field elements only if you are sure that those values won't grow too much.
Big nums
Numbers large enough to represent the domain of approximately 256-bit hash digests.
lurk-user> #0x1
[1 iteration] => #0x1
lurk-user> #0x4668b9badf58209537dbb62e132badc5bb7bbaf137a8daeeef550046634da8
[1 iteration] => #0x4668b9badf58209537dbb62e132badc5bb7bbaf137a8daeeef550046634da8
Commitments
Actual cryptographic commitments to Lurk data, as we shall see in the next chapter. It has the same domain range of big nums.
lurk-user> (comm #0x0)
[2 iterations] => #c0x0
Characters
Lurk uses UTF-8 encoding to represent characters internally. Meaning that a wide range of characters are accepted.
lurk-user> 'a'
[1 iteration] => 'a'
lurk-user> '€'
[1 iteration] => '€'
lurk-user> 'ệ'
[1 iteration] => 'ệ'
Strings
Very briefly, strings are sequences of characters. We can even extract the head and the tail of a string.
lurk-user> (car "abc")
[2 iterations] => 'a'
lurk-user> (cdr "abc")
[2 iterations] => "bc"
Cons (pair)
We've already covered pairs, but it's important to mention that pairs are of their own type.
lurk-user> (type-eq (cons 1 2) (cons 'a' nil))
[7 iterations] => t
Symbols
These are the typical language entities we use to bind values to, as in (let ((a 1)) a)
.
lurk-user> (type-eq 'a 'x)
[3 iterations] => t
t
and nil
are of this same type.
lurk-user> (type-eq t 'x)
[3 iterations] => t
lurk-user> (type-eq t nil)
[3 iterations] => t
Keywords
Keywords are self-evaluating symbols without special semantics like t
or nil
.
lurk-user> :foo
[1 iteration] => :foo
lurk-user> (type-eq :foo :bar)
[3 iterations] => t
Built-ins
These are symbols in the .lurk.builtin
package (more on packages later).
lurk-user> (type-eq 'cons 'let)
[3 iterations] => t
lurk-user> (type-eq 'cons 'x)
[3 iterations] => nil
Coroutines
Symbols specified in a Lang
used to call coroutines (more on this later).
Functions
Functions are the result of evaluated lambda
expressions.
lurk-user> (type-eq (lambda () nil) (lambda (x) (+ x 1)))
[3 iterations] => t
Undersaturated functions, that is, functions that have not been supplied all of their arguments, are still of the same type.
lurk-user>
(type-eq
(lambda (x) (+ x 1))
((lambda (x y) (+ x y)) 1))
[5 iterations] => t
Environments
Environments carry pairs of symbol/value bindings. Every expression evaluation in Lurk happens with respect to a certain environment.
The starting environment in the REPL is empty by default.
lurk-user> (current-env)
[1 iteration] => <Env ()>
The environment grows as we create new bindings.
lurk-user> (let ((a 1) (b 2)) (current-env))
[4 iterations] => <Env ((b . 2) (a . 1))>
Later, we will learn how to change the REPL's environment.