user=> (def x 1)
user=> (def y 2)
user=> ^{:x x} [x y 3]
^{:x 1} [1 2 3]
Вычисление может происходить в различных контекстах:
Интерактивно, в REPL
На последовательности чтения форм из потока, с помощью load
/ load-file
/ load-reader
/ load-string
Программно, с помощью eval
Программы Clojure состоят из выражений. Каждая форма, не обработанная специально какой-либо специальной формой или макросом, рассматривается компилятором как выражение, которое вычисляется чтобы получить значение. Нет объявлений или операторов, хотя иногда выражения могут быть вычилены из-за побочных эффектов и их значение игнорируется. В любом случае, при вычислении один объект рассматривается компилятором, вычисляется и его результат возвращается. Если нужно скомпилировать выражение, это будет сделано. Нет отдельных этапов компиляции и нет необходимости быспокоиться что функция, которую вы определили будет проинтерпретирована. Clojure не имеет интерпретатора.
Строки, числа, символы, true
, false
, nil
и ключевые значения вычисляются в самих себя.
Знаки разрешаются:
Если это знак с уточненным пространством имен, его значение - это значение глобальной переменной называемой этим символом. Если такой переменной не существует или она существует, но не является публичной или принадлежит к другому пространству имен - это ошибка.
Если это знак с уточненным пакетом, его значение - это Java класс назваемый символом. Если такого класса нет - это ошибка.
Иначе, если знак не уточнен и первое из следующего применимо:
Если он называет специальную форму, то он рассматривает специальную форму и должен быть соответственно использован.
Поиск выполняется в текущем пространстве имен чтобы найти есть ли соответствие от знака к классу. Если так, знак рассматривается как имя Java объект. Кстати, имена классов обычно обозначают объекты Class, но обрабатываются специальным образом в некоторой специальной форме, например .
и new
.
Если локально (т.е. в определении функции), поиск выполняется, чтобы найти если знак именует локальную переменную (например, аргумент функции). Если так, значение - это значение локальной переменной.
Поиск выполняется в текущем пространстве имен и ищется, есть ли соответствие знака и переменной. Если так, значение - это значение привязанное к этой переменной.
Иначе это ошибка.
Если знак имеет метаданные, они могут быть использованы компилятором, но не будут частью результата.
Вектора, множества и соответствия дают вектора, (хэш-) множества и соответствия, чье содержимое - это вычисленные значения объектов, которые они содержат. Элементы векторов вычисляются слева направа, множества и соответствия - вычисляются в неопределенном порядке. То же верно и для соответствий метаданных. Если вектор или соответствие имеет метаданные, вычисленные метаданные будут становиться метаданными значения-результата.
user=> (def x 1)
user=> (def y 2)
user=> ^{:x x} [x y 3]
^{:x 1} [1 2 3]
Пустой список ()
вычисляется в пустой список.
Непустые списки рассматриваются как вызовы к специальным формам, макросам или функциям. Вызов имеет форму (оператор операнды*).
Специальные формы - это примитивы встроенные в Clojure, которые осуществляют базовые операции. Если оператор вызова является знаком, который разрешается в имя специальной формы, вызов является вызовом специальной формы. Каждая форма обсуждается индивидуально в Специальных Формах.
Макросы - это функции, которые воздействуют на формы, разрешая синтаксические абстракции. Если оператор вызова является знаком, который именует глобальную переменную, которая является функцией-макросом , эта макро-функция вызывается и передается невычисленные формы операндов. Возвращаемое значение макроса затем вычисляется на его месте.
Если оператор не является специальной формой или макросом, вызов рассматривается как вызов функции. И оператор и операнды, если они есть, вычисляются слева направо. Результат вычисления оператора затем приводится к IFn (интерфейс, представляющий функции Clojure), и на нем вызывается invoke(), которому передаются вычисленные аргументы. Возвращаемое значение invoke() - это значение выражения вызова. Если форма вызова функции имеет метаданные, они могут быть использованы компилятором, но не будут частью результата. Заметим, что специальные формы и макросы могут иметь отличающееся от нормального вычисление их аргументов, как описано в соответствующих разделах в Специальных Формах.
Любой объект, отличающийся от описаных будет вычисляться в самого себя.
(load classpath-resource …)
(load-file filename)
(load-reader reader)
(load-string string)
Выше описано вычисление одной формыю Различные load формы будут последовательно прочитаны и вычислены в множество форм содержащихся в исходном коде. Такие множества форм обычно имеют побочные эффекты, часто в глобальном окружении, определений функций и т.д.
Функции загрузки возникают во временном контексте, в котором *ns* имеет свежую привязку. Это означает, что любая форма должна иметь эффект на эту переменную (например in-namespace), эффект будет снят после завершения загрузки. load и иже с ним возвращают значение, произведенное последним выражением.
(eval form)
Вычисляет форму структур данных (не текст!) и возвращает результат.
(eval (list + 1 2 3))
-> 6