user=> (map (fn [x] (.toUpperCase x)) (.split "Dasher Dancer Prancer" " "))
("DASHER" "DANCER" "PRANCER")
Clojure имеет богатый набор структур данных, которые объединяют следующие свойства:
Они неизменяемы
Они предназначены для чтения
Они правльно поддерживают метод equals
Они предоставляют качественные hash-значения
Также, коллекции:
Управляются через интерфейсы.
Поддерживают итерирование
Поддерживают сохраняемые манипуляции.
Поддерживают метаданные
Реализуют java.lang.Iterable
Реализуют обязательные методы (для чтения) класса java.util.Collection
nil - это возможное значение любого типа данных в Clojure. nil имеет то же самое значение, что и null в Java. Система проверки условий Clojure основана на nil и false, которые представляют логическую ложь, когда любое другое значение представляет логическую истину. Также, nil используется как маркер конца последовательности в протоколе последовательностей.
Clojure по-умолчанию предоставляет полную поддержку значений-примитивов JVM, что позволяет реализовывать высокопроизводительный код для вычислительных приложений.
Также Clojure поддерживает классы-обертки Java, наследованные от java.lang.Number, включая BigInteger и BigDecimal, плюс свой собственный класс Ratio. Существуют следующие особенности:
По-умолчанию Clojure оперирует натуральными числами как экземплярами примитивного типа long. Когда примитивные операции возвращают значение, которое не вмещается в long генерируется java.lang.ArithmeticException. Clojure предоставляет множество альтернативных операторов, заканчивающися апострофом: +', -', *', inc', и dec'. Эти операторы автоматически преобразуют число в BigInt при переполнении, но работают медленнее, чем обычные операторы.
Представляет отношение между целыми числами. Результат деления целых чисел, который сам не является целым числом порождает дробь, т.е. 22/7 = 22/7, а не число с плавающей точкой и не число, обрезанное до целого.
Объекты BigInt и типы чисел с плавающей точкой "заражают драгих" при операциях. Например, любые целочисленные операции, включающие BigInt будут в результате давать BigInt, а любые операции включающие double или float будут в результате давать double.
Целочисленные литералы для BigInt и BigDecimal определяются при помощи суффиксов N и M соответственно.
Выражение |
---|
Возвращаемое значение |
|
|
|
|
|
|
|
|
|
|
|
|
Вычисление: + - * / inc dec quot rem min max
Автоматически расширяющие вычисления: +' -' *' inc' dec'
Сравнение: == < ⇐ > >= zero? pos? neg?
Битовые операции: bit-and bit-or bit-xor bit-not bit-shift-right bit-shift-left
Соотношения: numerator denominator
Приведение: int bigdec bigint double float long num short
Строки в Clojure являются объектами-строками Java. См. также Печать.
user=> (map (fn [x] (.toUpperCase x)) (.split "Dasher Dancer Prancer" " "))
("DASHER" "DANCER" "PRANCER")
Символы в Clojure такие же как в Java.
Ключевые слова - символические идентификаторы, которые разрешаются сами в себя. Они позволяют значительно быстрее производить проверку на равенство. Как символы, они имеют имена и необязательное пространство имен, оба из которых являются строками. Начальный ':' не является частью пространства имен или имени.
Ключевые слова реализуют интерфейс IFn, чтобы вызывать invoke() с одним аргументом (ассоциативным массивом) и необязательным вторым аргументом (значение по-умолчанию). Например (:mykey my-hash-map :none)
означает тоже самое, что (get my-hash-map :mykey :none)
. См. get.
Символы - это идентификаторы, которые обычно используются, чтобы на что-то ссылаться. Они моугт быть использованы в формах программы, чтобы ссылаться на параметры функции, устанавливать связи, имена классов и глобальные переменные. У них есть имена и необязательное пространство имён, оба - строки. Символы могут иметь метаданные (см. with-meta).
Символы, как и ключевые слова, реализуют интерфейс IFn, чтобы вызывать invoke() с одним аргументом(ассоциативным массивом) и необязательным вторым аргументом (значение по-умолчанию). Например ('mysym my-hash-map :none)
означает то же самое что (get my-hash-map 'mysym :none)
. См. get.
symbol symbol? gensym (см. также макрос считывателя с суффиксом \#)
Все коллекции Clojure являются неизменяемыми и стойкие. В частности, коллекции Clojure поддерживают эффективное создание 'модифицированных' версий, с использованием структурного обмена. Коллекции эффективны и потоконезависимы. Коллекции представлены в виде абстракций, и у них может быть одна или несколько конекретных реализаций. В частности, так как операции 'модификации' порождают новую коллекцию, эта новая коллекция может не иметь тот же самый тип, что исходная, но будет реализовывать тот же логический тип (интерфейс).
Все коллекции поддерживают count чтобы узнать размер коллекции, conj для 'добавления' элементов и seq чтобы получать последовательность, которая поможет пройти по всей коллекции, хотя их конкретное поведение немного отличается для различных типов коллекций.
Так как коллекции поддерживают функцию seq, все функции обработки последовательностей могут быть использованы с любой коллекцией.
Clojure предоставляет свой алгоритм вычисления хеш-суммы, немного лучше работающий с коллекциями и другими типами, известный как hasheq.
Интерфейс IHashEq
помечает коллекции, которые предоставляют функцию hasheq()
- для вычисления значения hasheq. Также, в Clojure функция hash может быть использована, чтобы вычислить значение hasheq.
Упорядоченные коллекции (векторы, списки, последовательности и т.д.) обязаны использовать следующий алгоритм для вычисления haseq (где hash вычисляет hasheq). Заметим, что unchecked-add-int и unchecked-multiply-int используются для выполнения операций над целыми числами с переполнением.
(defn hash-ordered [collection]
(-> (reduce (fn [acc e] (unchecked-add-int
(unchecked-multiply-int 31 acc)
(hash e)))
1
collection)
(mix-collection-hash (count collection))))
Неупорядоченные коллекции (ассоциативные массивы, множества) обязаны использовать следующий алгоритм для вычисления hasheq. Элемент ассоциативного массива рассматривается как упорядоченная коллекция, состоящая из ключа и значения. Заметим, что unchecked-add-int используется для выполнения операций над целыми числами с переполнением.
(defn hash-unordered [collection]
(-> (reduce unchecked-add-int 0 (map hash collection))
(mix-collection-hash (count collection))))
Алгоритм работы mix-collection-hash - это деталь реализации и может изменяться.
Вектор - это пронумерованная коллекция значений. Вектора поддерживают доступ к элементу по его номеру за log32N шагов. count - за время O(1). conj помещает элемент в конец вектора. Вектора также поддеживают rseq, которая возвращает элементы в обратном порядке. Вектора реализуют IFn, для invoke() с одним аргументом, который рассматривается как номер. В качестве результата возвращается элемент, соответствующий этому номеру. Таким образом, вектора - это функции, переводящая номер в элемент. Вектора сначала сравниваются по длине, потом поочередно сравниваются элементы.
Ассоциативный массив - это коллекция, которая ставит в соответствие ключи и значения. Предоставляются два типа ассоциативных массивов - сортированные и хэшированные. Хэшированные массивы требуют, чтобы ключи поддерживали hashCode и equals. Сортированные массивы требуют, чтобы ключи реализовывали Comparable, или экземпляр класса Comparator. Хэшированные массивы предоставляют более быстрый доступ log32N шагов против logN шагов, но сортированные массивы зато отсортированы. count - сложность O(1). conj ожидает другой ассоциативный массив (возможно состоящий из одной записи), как аргумент, и возвращает новый массив, содержащий записи из старого и из нового, причем новые записи перезаписывают старые. conj также принимают MapEntry или вектор из двух элементов (ключ и значение). seq возвращает последовательность записей массива, т.е. пары ключ-значение. Сортированные массивы также поддерживают rseq, которая возвращает записи в обратном порядке. Ассоциативные массивы реализуют IFn, для invoke() с одним аргументом (ключем) с опциональным вторым аргументом (значение по-умолчанию), то есть ассоциативные массивы - это функции их ключей. Значечния ключей и значений nil разрешены.
Создать новый ассоциативный массив: hash-map sorted-map sorted-map-by
'Изменить' массив: assoc dissoc select-keys merge merge-with zipmap
Просмотреть ассоциативный массив: get contains? find keys vals map?
Просмотреть элемент массива: key val
В большинстве случаев, вместо использования StructMap лучше использовать records.
Часто несколько экземпляров ассоциативных массивов имеют один и тот же набор ключей, например когда они используются также, как струкутры или объекты в других языках программирования. StructMap поддерживают этот вариант использования, эффективно переиспользуя информацию о ключах, а также предоставляя опциональные методы доступа к этим ключам с повышенной производительностью. StructMap во всех отношениях являются ассоциативными массивами, поддерживающими тот же набор функций, совместимыми с другими ассоциативными массивами, и являются расширяемыми (т.е. они не ограничены своими базовыми ключами). Единственное ограничение - вы не можете отделить StructMap от одного из его базовых ключей. StructMap будет по прежнему хранить свои базовые ключи.
Чтобы создать StructMap нужно сначала создать базовый объект структуры с помощью create-struct или defstruct, затем создать экземпляры с помощью struct-map или struct.
(defstruct desilu :fred :ricky)
(def x (map (fn [n]
(struct-map desilu
:fred n
:ricky 2
:lucy 3
:ethel 4))
(range 100000)))
(def fred (accessor desilu :fred))
(reduce (fn [n y] (+ n (:fred y))) 0 x)
-> 4999950000
(reduce (fn [n y] (+ n (fred y))) 0 x)
-> 4999950000
Объявление StructMap: create-struct defstruct accessor
Создать отдельную структуру: struct-map struct
При выполнении манипуляций с кодом часто хочется иметь ассоциативный массив поддерживающий упорядоченные ключи. ArrayMap это как раз такой ассоциативный массив - он просто реализован как массив пар ключ-значение. Скорость поиска в нем возрастает по линейному закону и он подходит только для очень маленьких ассоциативных массивов. Он реализует интерфейс ассоциативных массивов целиком. Новый ArrayMap может быть создан с помощью функции array-map. Также ArrayMap будет поддерживать порядок пока не 'модифицирован'. Последующее изменение заставит его 'превратиться' в хэшированный ассоциативный массив.
Множества - это коллекции уникальных значений.
Существует поддержка множества с помощью литералов:
#{:a :b :c :d}
-> #{:d :a :b :c}
Вы можете создать множество с помощью функций hash-set и sorted-set:
(hash-set :a :b :c :d)
-> #{:d :a :b :c}
(sorted-set :a :b :c :d)
-> #{:a :b :c :d}
Вы также можете получить множество значений коллеции с помощью функции set:
(set [1 2 3 2 1 2 3])
-> #{1 2 3}
Множества - это коллекции:
(def s #{:a :b :c :d})
(conj s :e)
-> #{:d :a :b :e :c}
(count s)
-> 4
(seq s)
-> (:d :a :b :c)
(= (conj s :e) #{:a :b :c :d :e})
-> true
Множества поддерживают 'удаление' с помощью disj, а также contains? и get, последний возвращает объект, который содержится внутри множества и равен ключу, если таковой имеется:
(disj s :d)
-> #{:a :b :c}
(contains? s :b)
-> true
(get s :a)
-> :a
Множества - это функции своих членов, что реализовано с помощью get:
(s :b)
-> :b
(s :k)
-> nil
Clojure предоставляет базовый набор операция типа union / difference / intersection, также как поддержку некоторый псевдо-реляционной алгебры для 'отношений', которые являются просто множетсвом ассоциативных массивов - select / index / rename / join.