Clojure

Специальные Формы

(def symbol init?)

Создает и размещает или распологает глобальную переменную с именем symbol в пространстве имен, находаемся в (*ns*) текущего пространства имен. Если init предоставлен, он вычисляется, и начальная привязка переменной устанавливается в результат. Если init нет, начальная привязка не изменяется. def всегда применяется к начальной привязке, даже если переменная привязана к потоку в момент когда def вызывается. def производит переменную саму по себе (не её значение). Выбрасывает исключение если знак уже существует в пространстве имен и не соответствует переменной. С версии 1.3, def имеет необязательный переметр doc-string: (def symbol doc-string? init?).

Любые метаданные знака будут вычислены и станут метаданными переменной. Есть несколько ключей метаданных, имеющих специальное значение:

  • :private

    логическое значение, обозначающее контроль доступа к переменной. Если этого ключа нет, по-умолчанию будет применен доступ public (также, если бы было :private false).

  • :doc

    строка, содержащая короткую документацию для содержимого переменной

  • :test

    функция без аргументов которая использует assert чтобы проверить различные операции. Переменная будет доступна во время вычисления литерала fn в метаданных.

  • :tag

    знка именующий класс или объект Class который обозначает Java-тип объекта в переменной, или возвращаемого значения, если объект является функцией.

Также компилятор будет распологать следующие метаданные в переменной:

  • :file строка

  • :line целое число

  • :name простой знак

  • :ns пространство имен в которое переменная была включена

  • :macro true если переменная именует макрос

  • :arglists список векторов форм аргументов, так как они были переданы в defn

Метаданные переменной могут быть использованы для различных целей. Рассмотрим использование ключей с уточненным пространством имен (например :myns/foo) чтобы избежать конфликтов.

(defn
 ^{:doc "mymax [xs+] gets the maximum value in xs using > "
   :test (fn []
             (assert (= 42  (mymax 2 42 5 4))))
   :user/comment "this is the best fn ever!"}
  mymax
  ([x] x)
  ([x y] (if (> x y) x y))
  ([x y & more]
   (reduce mymax (mymax x y) more)))

user=> (meta #'mymax)
  {:name mymax,
   :user/comment "this is the best fn ever!",
   :doc "mymax [xs+] gets the maximum value in xs using > ",
   :arglists ([x] [x y] [x y & more])
   :file "repl-1",
   :line 126,
   :ns #<Namespace user >,
   :test #<user$fn__289 user$fn__289@20f443 >}

Многие макросы расширяют в def (например defn, defmarco) и таким образом также используют метаданные для получаемого значения из знака использованного как имя.

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

(if test then else?)

Вычисляет test. Если получается не nil или false, вычисляет и возвращает then, иначе вычисляет и возвращает else. Если else не предоставлен, вместо него возвращается nil. Все другие условные выражения в Clojure основаны на той же логике, т.е. nil и false представляют логическую ЛОЖЬ, остальное - логическую ИСТИНУ. if выполняет проверку условия, представленного в виде Java-метода, возвращающего значение без преобзразования в Boolean. Заметим, что if не проверяет произвольные значения java.lang.Boolean, только false (Java Boolean.FALSE) т.е. если вы создали свои обертки Boolean удостовертесь, что используете Bolean/valueOf, а не Boolean конструктуры.

(do exprs*)

Вычисляет выражения по порядку и возвращает последнее значение. Если выражений нет, возвращает nil.

(let [bindings* ] exprs*)

binding ⇒ binding-form init-expr

Вычисляет выражения в лексическом контексте, в котором знаки из binding-form привязаны к их соответствующим значениям init-expr. Привязки последовательны и следующая может видеть предыдущие. Выражения содержатся неявно. Если знак привязки аннотирован тегом метаданных, компилятор будет пытаться разрешить тег в имя класса и предположить, что тип в последующих ссылках к привязке. Простейшая форма привязки - это знак которй привязывается к целому init-expr:

(let [x 1
      y x]
  y)
-> 1

См. Формы привязки чтобы узнать больше о формах привязки.

Локальные привязки, созданные с помощью let не являются переменными. Однажды созданные, их значения никогда не меняются!

(quote form)

Порождает невычисленную форму.

user=> '(a b c)
(a b c)

Заметим, что не будет сделано попыток вызвать функцию a. Возвратится список из 3 знаков.

(var symbol)

symbol должен разрешаться в переменную и тогда объект переменная (не её значение) будет возвращен. Макрос #'x разворачивается в (var x).

(fn name? [params* ] exprs*)

(fn name? ([params* ] exprs*)+)

params ⇒ positional-params* , или positional-params* & rest-param
positional-param ⇒ binding-form
rest-param ⇒ binding-form
name ⇒ symbol

Определяет функцию (fn). Функции объекты, реализующие интерфейс IFn. Этот интерфейс определяет функцию invoke(), которая перегружается с арностью от 0 до 20. Один объект fn может реализовывать один или более метод invoke() и быть таким образом перегруженным по арности. Одна и только одна перегрузка может принимать множество параметров, если через амперсанд объявлены rest-param. Когда такая точка входа со множеством параметров вызывается с большим количеством параметров, они будут собраны в последовательность rest-param. Если количество аргументов не превышает positional params, rest-params будут равны nil.

Первая форма, определяет fn с одним invoke методом. Вторая определяет fn с одним или более перегруженными invoke методами. Арность перегрузок должна быть ясна. Иначе результатом выражения будет один fn объет.

Выражения вычисляются в окружении, в котором параметры привязываются к аргументам. Выражения exprs оборачиваются в неявный do. Если предоставлен знак name, он привязывается внутри объявления функции к объекту функции, позволяя самовызов, даже в анонимной функции. Если знак param аннотирован метаданными, компилятор будет пытаться разрешить тег в имя класса и предположить этот тип в последующих ссылок на связывание.

(def mult
  (fn this
      ([] 1)
      ([x] x)
      ([x y] (* x y))
      ([x y & more]
          (apply this (this x y) more))))

Заметим, что именованные функции, такие как mult обычно определяются с помощью defn, который раскрывается в конструкцию, похожую на представленную выше.

Функции определяют точку рекурсии вверху функции, с арностью равной количеству параметров включая rest param, если он есть. См. recur.

Функции реализуют Java-интерфейсы Callable, Runnable и Comparator.

После 1.1

Функции поддерживают определение пред- и постустовия времени выполенения.

Синтаксис для определения функции следующий:

(fn name? [params* ] condition-map? exprs*)

(fn name? ([params* ] condition-map? exprs*)+)

Расширение синтаксиса также применяется к defn и другим макросам, которые разворачиваются в fn формы.

Заметим: если единственная форма после вектора параметров - соответствие, оно рассматривается как тело функции, а не как соответствие условий.

Соответствие условий (condition-map) может быть использовано для определения перд- и постусловие для функции. Это одна из следующих форм:

{:pre [pre-expr*]
:post [post-expr*]}

Где ключ опционален. Условия также могут быть предоставлены как метаданные списка аргументов.

pre-expr и post-expr - это логические выражения, которые могут ссылаться на параметры функции. В дополнении, % может быть использовано в post-expr чтобы сослаться на возвращенное значение. Если любое из условий вычисляется в false и *assert* - true, бросается assertion failure исключение.

Пример:

(defn constrained-sqr [x]
    {:pre  [(pos? x)]
     :post [(> % 16), (< % 225)]}
    (* x x))

См. Формы привязки чтобы получить больше информации о формах привязки.

(loop [bindings* ] exprs*)

loop - это тоже, что и let, но он устанавливает точку рекурсии на вершину цикла, с арностью равной количеству binding-ов. См. recur.

(recur exprs*)

Вычисляет выражения по порядку, затем паралельно переопределяет привязки точки рекурсии в значение выражений. Если точкой рекурсии был fn, то перепривязываются параметры. Если точкрй рекурсии был цикл, переопределяются привязки цикла. Затем выполнение продолжается с точки рекурсии. Выражение recur должно соответствовать по арности точке рекурсии. В частности, если точкой рекурсии был fn с изменяемым количеством параметров, не происходит сбор оставшихся аргументов - должен быть передан просто seq (или null), иначе будет ошибка.

Заметим, что recur - единственная конструкция-цикл без затрат стека в Clojure. Оптимизация хвостовой рекурсии не поддерживается и использование самовызовов для реализации цикла неизвестной длины является нехорошей практикой. Использование recur для хвостой рекурсии проверяется компилятором.

(def factorial
  (fn [n]
    (loop [cnt n acc 1]
       (if (zero? cnt)
            acc
          (recur (dec cnt) (* acc cnt))))))

(throw expr)

Выражение вычисляется и выбрасывается, соответственно оно должно породить объект-наследник Throwable.

(try expr* catch-clause* finally-clause?)

catch-clause → (catch classname name expr*)
finally-clause → (finally expr*)

Выражения вычисляются и, если не произошло исключений, возвращается значение последнего выражения. Если произошло исключение и предоставлены формы catch-clause, то каждое из них проверяется по порядку в поиске формы, у которой в качестве classname указан класс, к которому может быть приведено выброшенное исключение. Эта форма catch-clause рассматривается как "подходящая". Если подходящая форма catch-clause найдена, то её выражения expr вычисляются в контексте, в котором к имени name привязано исключение, и значение последнего выражение будет являться возвращаемым значением всей формы try. Если нет подходящих catch-clause, исключение выбрасывается наружу. Перед завершением, нормальным или нет, будет вычислено финальное выражение finally-clause.

(monitor-enter x)

(monitor-exit x)

Это примитивы для синхронизации, применения которых в пользовательском коде следует избегать. Используйте макрос locking.

Другие специальные формы

Специальные формы dot ('.'), new, и set! полей описаны в разделе Java Interop.

Форма set! переменных описана в разделе Переменные.

Формы реструктуризирующего присваивания

Clojure поддерживает астрактное структурное присваивание, часто называющееся реструктуризирующим, в списках присваивания, списках параметров fn и в любом макросе, который раскрывается в let или fn. Основная идея - форма присваивания может быть литералом структуры данных, содержащим символы, которые нужно привязать к соответствующим частям начального значения. При присваивании литерал вектор может быть привязан к любой последовательности, а ассоциативный массив - к любой ассоциативной структуре.

Реструктуризирующее присваивание векторов

Реструктуризирующее присваивание векторов позволяет вам присваивать имена элементам упорядоченных множеств: векторов, списков, последовательностей, строк, массивов и всего, что поддерживает функцию nth. Реструктуризирующее присваивание векторов состоит из вектора символов, которые нужно присвоить элементам последующего упорядоченного множества начальных значений, получаемым с помощью функции nth. Также в списке символов может присутствовать &, что означает что следующему за ним символу должен быть присвоен в качестве значения список всех оставшихся начальных значений, получаемых с помощью nthnext .

Наконец, можно использовать :as с последующим символом. Данному символу будет присвоен в качестве значения весь список начальных значений.

(let [[a b c & d :as e] [1 2 3 4 5 6 7]]
  [a b c d e])

->[1 2 3 (4 5 6 7) [1 2 3 4 5 6 7]]

Эти формы могут быть вложенными:

(let [[[x1 y1][x2 y2]] [[1 2] [3 4]]]
  [x1 y1 x2 y2])

->[1 2 3 4]

Также работает со строками:

(let [[a b & c :as str] "asdjhhfdas"]
  [a b c str])

->[\a \s (\d \j \h \h \f \d \a \s) "asdjhhfdas"]

Реструктуризирующее присваивание ассоциативных массивов

Формы присваивания ассоциативных массивов позволяют присваивать имена к элементам соответствий (не обязательно ассоциативных массивов), таких как ассоциативные массивы, векторы, строки или массивы (у последних трёх ключами будут являться целочисленные индексы). Формы присваивания состоят из пар соответствий ключ - присваиваемая форма, каждому символу будет присвоено значение соответствующего начального выражения. Можно использовать в форме присваивания ключ :as, за которым указать символ, которому затем будет присовено все начальное значение. Также можно использовать ключ :or, за которым указать другой ассоциативные массив, в котором будет производится поиск значений, не присутствующих в первом массиве начальных значений.

(let [{a :a, b :b, c :c, :as m :or {a 2 b 3}}  {:a 5 :c 6}]
  [a b c m])

->[5 3 6 {:c 6, :a 5}]

Часто, если вы хотите присвоить одноименные символы ключам. Директива :keys позволит избежать избыточности.

(let [{fred :fred ethel :ethel lucy :lucy} m] ...

может быть записано:

(let [{:keys [fred ethel lucy]} m] ...

Как в Clojure 1.6, вы можете использовать ключи с префиксами в реструктуризирующем присваивании ассоциативных массивов:

(let [m {:x/a 1, :y/b 2}
      {:keys [x/a y/b]} m]
  (+ a b))

-> 3

Как показано выше, в случае использования ключей с префиксами, имя присваиваемого символа будет таким же, как в правой части ключа. Вы также можете использовать авторазрешение форм в директиве :keys:

(let [m {::x 42}
      {:keys [::x]} m]
  x)

-> 42

Есть аналогичные директивы :strs и :syms, чтобы работать со строками и ключами-символами. последний также позволяет рабобатть с символами с префиксами начиная с Clojure 1.6.

Вложенное реструктуризирующее присваивание

Так как формы присваивания могут быть произвольно вложенными друг в друга, вы можете делать что угодно:

(let [{j :j, k :k, i :i, [r s & t :as v] :ivec, :or {i 12 j 13}}
      {:j 15 :k 16 :ivec [22 23 24 25]}]
  [i j k r s t v])

-> [12 15 16 22 23 (24 25) [22 23 24 25]]