✓ Homoiconicity: Data First, Syntax Second

Homoiconic syntax

Lisp in general has a unique property known as homoiconicity. This is when the syntax of the language is 100% data definitions. Clojure is an homoiconic language.

Homoiconcity

Definition

Let \(L\) be the syntactic definitions of all programming constructs of the programming language.

We can separate the definitions into two categories: programming \(L_\mathrm{prog}\) and data \(L_\mathrm{data}\).

\[ L = L_\mathrm{prog} \cup L_\mathrm{data} \]

The language is homoiconic if

\[ L_\mathrm{prog} \subseteq L_\mathrm{data} \]

Consequence

The consequence is:

\[ L = L_\mathrm{data} \]

Namely, all programs of a homoiconic language are data.

  • Programs can parse programs

  • Programs can transform programs

  • Programs can generate programs

Scalars

Numbers

  • Integers

  • Floats

  • BigDecimal

;;
;; An integer
;;
123
123
;;
;; A float
;;
3.1415
3.1415
;;
;; A bigdecimal
;;
3.1415M
3.1415M

Negation

We should note that

-3.1415

is not equivalent to

- 3.1415
- 3.1415
3.1415
3.1415
3.1415

Strings and characters

Strings are with double quotes. Multilines are supported.

"hello
world
in multiple lines"
"hello\nworld\nin multiple lines"

Character literals start with \....

\h
\h

Keywords

Clojure has keywords as a special type of scalar. Keyword literals look like :...

:last-name
:last-name

Data Structures

Lists

Lists are linked lists. Elements in a list can be anything including other lists. The syntax of lists is just whitespace separated list of expressions enclosed by parenthesis.

( ... )

Vectors

Vectors are dynamic arrays. They differ from lists mainly from a performance perspective:

  • List append and element access are done in \(\mathcal{O}(n)\).

  • Vector append and element access are done in \(\mathcal{O}(1)\).

The syntax of vectors is whitespace separated elements enclosed by square brackets.

[ ... ]

Sets

Sets are collections of elements with the properties:

  • all elements in a set must be all distinct

  • different ordering of elements does not change the set

The syntax of sets is:

#{ ... }

Maps

Maps are hash-maps containing key-value pair entries. The keys in a map must uniquely identify the entry.

The syntax of map literals is a whitespace separated of even number of expressions of key/value pairs, enclosed by {}.

{ key1 val1 key2 val2 key3 val3 ... }

Programming Constructs

Clojure provides a runtime environment built on top of the Java Virtual Machine (JVM). Clojure itself extends JVM in several important ways as well (e.g. lock-free concurrency using transactional memory and reactive programming).

To perform basic data processing, computation, and runtime activities, one needs to compose programs using different programming constructs.

Some examples of programming constructs:

  • Function declaration and invocation

  • Declaration of symbols

  • Loops

  • Branches

  • A lot more…

Homoiconicity again

Clojure models its programming constructs as data. This eliminates any need for specialized syntax for programming.

  • By default, lists are programs (not data).

  • The first element of a list determines the programming construct.

Function declaration expressions

(fn ...)
  • a list of symbols for the parameters: this is done using a vector.

  • an expression as the body: this is the return value.

(fn [ <parameter-list> ] <body>)
(fn [x y z] (+ x y z))
#function[user/eval5374/fn--5375]

Function invocation

  • Lists are, by default, invocations.

( <function-expression> x y z ... )
(+ 1 2 3)
6
((fn [x y z] (+ x y z)) 1 2 3)
6

Top-level symbol binding

(def <name> <expression>)
(def add-triple (fn [x y z] (+ x y z)))
#'user/add-triple
(add-triple 1 2 3)
6