Базовый синтаксис

Вы можете воспользоваться сервисами https://repl.it и http://clojurescript.io, чтобы поэкспериментировать с языком прямо в браузере без установки окружения на свой компьютер.

Цель этих примеров - научиться читать, но не писать код на clojure.

;; комментарий

Печать на экран. prn - функция печати на экран. "hello world" - строка, аргумент функции.

(prn "hello world")

В lisp используется только префиксная (польская) нотация, т.е. функция стоит всегда на первом месте, а за ней ее аргументы. Вместо функции может быть специальная форма или макрос, но не будем касаться этой темы. Иными словами на первом месте внутри скобок находится нечто, что будет вызвано.

Для примера сравним js и lisp:

x.method(y, z) -> (method x y z)
func(x, y, z) -> (func x y z)
x + y -> (+ x y)

В Clojure есть поддержка неймспейсов. Мы не будем разбирать их объявление, а сразу перейдем к вызову:

(some-ns/some-fn some-arg)

Clojure - язык не ленивый, и аргументы функции вычисляются до ее вызова. Рассмотрим на примере:

(prn (str "hello" " " "world"))

Сначала будет вычислен аргумент функции prn - (str "hello" " " "world"). str - функция конкатенации строк. Таким образом prn будет вызван так: (prn "hello world").

Для демонстрации языка воспользуемся утверждениями - assert. Если аргумент ложный, то будет брошено исключение, если истинный, то просто вернется nil.

(assert true) ;; #=> nil
;; (assert false) ;; AssertionError Assert failed: false
;; (assert nil)   ;; AssertionError Assert failed: nil

Например, я утверждаю, что 1 = 1:

(assert (= 1 1))

Привычные операторы могут принимать переменное количество аргументов:

(assert (= 1)) ;; всегда истинно для одного аргумента
(assert (= 1 1 1)) ;; 1 = 1 = 1

(assert (< 1)) ;; всегда истинно для одного аргумента
(assert (< 1 2 3)) ;; 1 < 2 < 3

Примитивные clojure типы - java типы. Проверим это с помощью функции class, возвращающей класс своего аргумента

(assert (= java.lang.String  (class "some string")))
(assert (= java.lang.Long    (class 1)))
(assert (= java.lang.Boolean (class true)))

Мы можем задать название некоторому значению с помощью специальной формы let:

(let [x 1]
  (assert (= 1 x)))

Отмечу, что x локальное обозначение, и вне let оно не существует.

Новое значение можно связать с тем же названием:

(let [x 1
      x 2]
  (assert (= 2 x)))

Или переопределить внешнее:

(let [+ -]
  (assert (= 0 (+ 1 1))))

В рамках текущего пространства имен можно объявить глобальное значение:

(def x 1)
(assert (= 1 x))

Определим анонимную функцию одного аргумента x и прибавляющую к нему единицу:

(let [f (fn [x] (+ 1 x))]
  (assert (= 3 (f 2))))

Если мы хотим объявить функцию в неймспейсе, то вместо (def f (fn [x] ...)) удобно воспользоваться макросом defn:

(defn f [x] (+ 1 x))
(assert (= 3 (f 2)))

Clojure поддерживает замыкания:

(let [x 1
      f (fn [y] (+ x y))]
  (assert (= 3 (f 2))))

Функции это значения:

;; `+` - функция, а не оператор
(assert (= 6 (reduce + [0 1 2 3])))

Для коротких функций есть краткая форма:

(let [x [0 1 2]
      x'  (map #(+ 2 %) x)
      x'' (map (fn [i] (+ 2 i)) x)]
  (assert (= x' x'')))

В отличие от императивных языков, в clojure нет присваивания, т.е. let создает не переменные, а только именует значения. Если бы это было присваивание, то функция f вернула бы 2:

(let [x 1
      f (fn [] x) ;; замыкание(closure)
      x 2]
  (assert (= 2 x))
  (assert (= 1 (f))))

Имена могут содержать некоторые спецсимволы и их комбинации:

(let [x 1
      x' x
      x? x
      ?x x
      x! x
      !x x
      +x+ x
      -x- x
      *x* x
      =x= x]
  (assert (= x x' x? ?x x! !x +x+ -x- *x* =x=)))

Массивы в Clojure называются векторами. Вектора поддерживают доступ по индексу и хранят произвольные типы:

(let [x [0 "str" true [1 2 3]]]
  (assert (= "str" (get x 1)))
  (assert (= 0 (first x)))
  (assert (= "str" (second x))))

Ассоциативные массивы или мапы хранят ключи и значения произвольных типов. Запятая в clojure - просто пробельный символ, который можно опускать, однако при записи хеша для удобства запятые используют для разделения пар ключ-значение:

(let [x {:key 1, "key" 2, 2 3, true "4", [1 2] "5", nil 6}]
  (assert (= 1 (get x :key)))
  (assert (= 6 (get x nil))))

:key - это специальный тип Keyword. Их частно используют как ключи в ассоциативных массивах. Аналог символов в ruby или атомов в erlang. Реализуют интерфейс функций, т.е. принимает map и возвращает ассоциированное с собой значение:

(let [x {:key "value"}]
  (assert (= "value" (:key x))))

Все clojure значения - неизменяемые. При изменении возвращается новый объект, старый остается доступен. При этом не происходит полного копирования, т.е. новая структура переиспользует старую.

;; pop - возвращает коллекцию без вершины
(let [x  [0 1 2 3]
      x' (pop x)]
  (assert (= [0 1 2 3] x))
  (assert (= [0 1 2] x')))

;; assoc - добавляет значения по их ключам
(let [x {}
      x' (assoc x :k1 1 :k2 2)]
  (assert (= {} x))
  (assert (= {:k1 1, :k2 2} x')))

Этот код можно выполнить прямо в браузере. Там доступна консоль, в которой можно выполнять произвольные clojure выражения.