Let survey the elements of Clojure constructs. Each programming construct is actually a fragment of Clojure’s data descriptive language.
A s-expression is defined recursively as:
( <atom>* )
are s-expressionsThe default semantics of s-expressions is function invocation:
In Python, we do:
print(a, b, c)
In s-expression we will have:
(print a b c)
Clojure extends s-expresses with its extended data structures:
We will refer to this extended s-expression syntax also as s-expressions.
Clojure programs start with a top level.
Actually, later on, we will see that Clojure program can (and usually do) have multiple top-level scopes associated with different namespaces.
Symbols can be defined at the top-level using the form.
(def pi 3.1415)
(def todos [{:priority 2
:title "Summer school"
:status :in-progress}
{:priority 1
:title "Course preparation"
:note "CSCI 2000U is a new course"
:status :incomplete}])
Top-level symbol bindings are discouraged except for top-level functions.
Scopes are nested, and they are created almost all the time. It might be useful to keep in mind the following:
let
form(let [ <sym> <val>
<sym> <val>
... ] ...)
Functions are also data, and you construct a function-value using
the (fn [...] ...)
form.
Here is a function-value that is declared, but not bound to any symbol.
(fn [a b c]
(let [n 3]
(/ (+ a b c) n)))
In side the fn form, we have four symbols. Note that there
is no bindings for a
, b
nor c
because these bindings
will be created only when the function is applied to given
arguments.
We can bind the function to a symbol so it can be used repeatedly.
(def avg-3 (fn [a b c]
(/ (+ a b c) 3)))