Clojure

Вычисление

Вычисление может происходить в различных контекстах:

  • Интерактивно, в REPL

  • На последовательности чтения форм из потока, с помощью load / load-file / load-reader / load-string

  • Программно, с помощью eval

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

Строки, числа, символы, true, false, nil и ключевые значения вычисляются в самих себя.

Знаки разрешаются:

  • Если это знак с уточненным пространством имен, его значение - это значение глобальной переменной называемой этим символом. Если такой переменной не существует или она существует, но не является публичной или принадлежит к другому пространству имен - это ошибка.

  • Если это знак с уточненным пакетом, его значение - это Java класс назваемый символом. Если такого класса нет - это ошибка.

  • Иначе, если знак не уточнен и первое из следующего применимо:

    1. Если он называет специальную форму, то он рассматривает специальную форму и должен быть соответственно использован.

    2. Поиск выполняется в текущем пространстве имен чтобы найти есть ли соответствие от знака к классу. Если так, знак рассматривается как имя Java объект. Кстати, имена классов обычно обозначают объекты Class, но обрабатываются специальным образом в некоторой специальной форме, например . и new.

    3. Если локально (т.е. в определении функции), поиск выполняется, чтобы найти если знак именует локальную переменную (например, аргумент функции). Если так, значение - это значение локальной переменной.

    4. Поиск выполняется в текущем пространстве имен и ищется, есть ли соответствие знака и переменной. Если так, значение - это значение привязанное к этой переменной.

    5. Иначе это ошибка.

Если знак имеет метаданные, они могут быть использованы компилятором, но не будут частью результата.

Вектора, множества и соответствия дают вектора, (хэш-) множества и соответствия, чье содержимое - это вычисленные значения объектов, которые они содержат. Элементы векторов вычисляются слева направа, множества и соответствия - вычисляются в неопределенном порядке. То же верно и для соответствий метаданных. Если вектор или соответствие имеет метаданные, вычисленные метаданные будут становиться метаданными значения-результата.

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