Basics¶
Though radicle
is designed with a particular use-case in mind–namely,
defining and interacting with decentralised state machines–it is ultimately a
general purpose language, quite similar to
Scheme or
Clojure. To effectively use it, it is helpful to learn the
basics of it independent of its application.
This chapter introduces radicle
as a language. In each section we present
a core datatype of the language, and show some basic operations that can be
performed on them.
Comments¶
The string ;;
starts a comment. This comments all the text until the next
line-break. In this guide we will often add a comment as follows:
(+ 1 1) ;; ==> 2
to indicate that an expression (in this case (+ 1 1)
) evaluates to a specific
value (in this case 2
).
Built-in datatypes¶
radicle
supports the following datatypes:
Booleans¶
The literal syntax for booleans is #t
for true, and #f
for false. Booleans can be combined with and
and or
:
(and #t #f) ;; ==> #f
(or #t #f) ;; ==> #t
Keywords¶
Keywords are identifiers that evaluate to themselves. To create a keyword you
prefix :
onto an identifier (:foo
is a keyword, for example). Keywords are
often used as keys in maps which act like records in other languages. The colon
at the front is a prefix form of the colon that follows the name of a field in
other languages. For example, {name: "foo", age: 22}
in JavaScript becomes
{:name "foo" :age 22}
in Radicle.
:foo ;; ==> :foo
Atoms¶
Atoms are identifiers which, when evaluated, lookup the value bound to that identifier in the current environment:
(def foo 42)
foo ;; ==> 42
Vectors¶
radicle
’s vectors are an array-like container for values. Literal notation
for vectors uses square brackets:
["this" "is" "a" "vector"]
You can access any of the elements in the vector using nth
:
(nth 2 ["start at zero!" "one" "two" "three"]) ;; ==> "two"
But be careful: if the size of the argument isn’t less than (<
) the size of the
vector then this will result in an error.
Concatenate vectors using <>
:
(<> [1 2 3] [4 5 6]) ;; ==> [1 2 3 4 5 6]
Add an element to the left using cons
:
(cons 0 [1 2 3]) ;; ==> [0 1 2 3]
Add an element to the right using add-right
:
(add-right 3 [0 1 2]) ;; ==> [0 1 2 3]
Lists¶
Lists are one of the fundamental data structures of functional programming. Just like vectors are enclosed in square brackets, lists are enclosed in parentheses.
However, as you may have noticed, parentheses are also used for function application. This is because lists are the representation for code that evaluates to the result of applying the first element of the list to the other elements.
Don’t worry if this is still a little unclear; we will discuss it at greater
length when we talk about evaluation and quotation. For now, what this in
practice means is that if you want to create a list, you can use the
function list
on the elements you want in the list. You can also use
list-from-vec
to make a vector into a list.
You can prepend an element to a list with cons
:
(cons 0 (list 1)) ;; ==> (0 1)
Retrieve the head of a list with head
:
(head (list 0 1 2 3)) ;; ==> 0
The rest of it with tail
:
(tail (list 0 1 2 3)) ;; ==> (1 2 3)
Or a specific index with nth
:
(nth 2 (list 0 1 2 3)) ;; ==> 2
Lists or vectors?¶
Vectors differ from lists in a couple of important ways. First, lists are
implemented as linked lists, and accessing the nth
element of a list has
linear time complexity on n
(the function nth
works for both vectors and
lists). Additionally, appending to the end of a list is also linear. Vectors,
on the other hand, allow for faster (logarithmic) indexed access and appending
(constant time).
Dictionaries¶
Dictionaries are mappings between keys and values. (You may know them as maps, associative arrays, or symbol tables.) Literal syntax for dictionaries is formed by including the keys and values inside curly braces:
{ :key1 1
:key2 "foo"
}
It is an error to have an odd number of elements inside the braces.
Keys may be any value, including other dictionaries:
{ :key1 1
{ :nested "foo" } "bar"
}
You can access the value associated with a key with the function lookup
:
(lookup :key1 { :key1 1 }) ;; ==> 1
And add new values with insert
:
(insert :key2 2 { :key1 1 }) ;; ==> { :key1 1 :key2 2 }
Simple expressions¶
Branching¶
To dispatch on a boolean value use if
:
(def num-bugs 50)
(if (> num-bugs 0)
"Oh no, so many bugs!"
"All done!")
To test multiple booleans use cond
:
(cond
(> num-bugs 100) "Oh no, so many bugs!"
(eq? num-bugs 0) "All done!"
#t "A few bugs.")
In fact all values which are not #f
are treated as true, so one can also
write:
(cond
(> num-bugs 100) "Oh no, so many bugs!"
(eq? num-bugs 0) "All done!"
:else "A few bugs.")
If none of the conditions in the cond
are true then the result is an error!
Function application¶
A function call takes prefixed, parenthesized form. That is, the call appears within parenthesis, with the function at the head, followed by its arguments:
(+ 3 2) ;; => 5
Functions¶
A function is created using fn
. The first argument is a vector of symbols
representing the parameters of the function, and the rest is a sequence of
expressions which are run when the function is called.
So a function which adds one to a number can be defined like so:
(fn [x] (+ x 1))
You can call it like any other function:
((fn [x] (+ x 1)) 1) ;; ==> 2
But most likely you want to def
ine a new variable:
(def f (fn [x] (+ x 1)))
(f 41) ;; ==> 42
Functions are not by default recursive. If you want to define a recursive
function, use the def-rec
form:
(def-rec range
(fn [from to]
(if (<= to from)
(list)
(cons from (range (+ 1 from) to)))))
Note that def-rec
may only be used to define functions.
For creating one-off anonymous functions quickly, there is a short-form. This is
formed by a \
character (meant to look like the main stroke of a λ) and then a
valid radicle expression, which will be the return value of the function. To
make a single parameter function use ?
for the argument. To make a
multi-parameter function, use ?1
, ?2
, etc. Here are some examples of
short-form lambdas and the long-form equivalent:
;; The identity function.
\?
(fn [x] x)
;; Partial application:
\(foo 0 ?)
(fn [x] (foo 0 x))
;; A function to make singleton vectors:
\[?]
(fn [x] [x])
;; Multiple arguments:
\(string-append ?1 " is the parent of " ?2)
(fn [x y] (string-append x " is the parent of " y))
;; Thunks:
\(put-str! "hello there!")
(fn [] (put-str! "hello there"))
Note that:
- Only
?1
,?2
, …,?9
are allowed for positional arguments. If you want more that 9 parameters please seek professional help. - Short-form lambdas cannot be nested.
- You cannot use a mixture of
?
and?1
,?2
, etc. inside a short-form lambda. - To avoid hard to detect bugs, atoms starting with a
?
followed by a sequence of digits that isn’t one of the allowed positional atoms will cause an error.