Буквальные восьмеричные числа
В какой-то момент я читал матрицу, в которой использовались ведущие нули для поддержания правильных строк и столбцов. Математически это правильно, поскольку начальный ноль, очевидно, не изменяет базовое значение. Попытки определить переменную с помощью этой матрицы, однако, загадочно потерпели бы неудачу с:
java.lang.NumberFormatException: Invalid number: 08
что полностью сбило меня с толку. Причина в том, что Clojure обрабатывает буквальные целые значения с ведущими нулями как восьмеричные числа, а восьмеричное число 08 отсутствует.
Я также должен упомянуть, что Clojure поддерживает традиционные шестнадцатеричные значения Java через префикс 0x . Вы также можете использовать любое основание от 2 до 36, используя обозначение «основание + r + значение», например, 2r101010 или 36r16, которые равны 42 основанию десять.
Попытка вернуть литералы в анонимном функциональном литерале
Это работает:
user> (defn foo [key val]
{key val})
#'user/foo
user> (foo :a 1)
{:a 1}
поэтому я считал, что это тоже сработает:
(#({%1 %2}) :a 1)
но это не удается:
java.lang.IllegalArgumentException: Wrong number of args passed to: PersistentArrayMap
потому что макрос читателя # () расширяется до
(fn [%1 %2] ({%1 %2}))
с литералом карты, заключенным в круглые скобки. Поскольку это первый элемент, он рассматривается как функция (которой на самом деле является буквальная карта), но никаких обязательных аргументов (таких как ключ) не предоставляется. Таким образом, анонимный функциональный литерал не расширяется до
(fn [%1 %2] {%1 %2})
и поэтому у вас не может быть никакого буквального значения ([],: a, 4,%) в качестве тела анонимной функции.
В комментариях приведены два решения. Брайан Карпер предлагает использовать конструкторы реализации последовательности (массив-карта, хеш-набор, вектор) следующим образом:
(#(array-map %1 %2) :a 1)
а Дэн показывает, что вы можете использовать функцию идентификации, чтобы развернуть внешнюю скобку:
(#(identity {%1 %2}) :a 1)
Предложение Брайана фактически подводит меня к моей следующей ошибке ...
Думая, что хеш-карта или массив-карта определяют неизменную реализацию конкретной карты
Обратите внимание на следующее:
user> (class (hash-map))
clojure.lang.PersistentArrayMap
user> (class (hash-map :a 1))
clojure.lang.PersistentHashMap
user> (class (assoc (apply array-map (range 2000)) :a :1))
clojure.lang.PersistentHashMap
Хотя, как правило, вам не нужно беспокоиться о конкретной реализации карты Clojure, вы должны знать, что функции, которые увеличивают карту - например, assoc или conc - могут принимать PersistentArrayMap и возвращать PersistentHashMap , который работает быстрее для карт большего размера.
Использование функции в качестве точки рекурсии, а не цикла для обеспечения начальных привязок
Когда я только начинал, я писал много таких функций:
(defn p3
([] (p3 775147 600851475143 3))
([i n times]
(if (and (divides? i n) (fast-prime? i times)) i
(recur (dec i) n times))))
Когда на самом деле цикл был бы более кратким и идиоматическим для этой конкретной функции:
(defn p3 [] {:post [(= % 6857)]}
(loop [i 775147 n 600851475143 times 3]
(if (and (divides? i n) (fast-prime? i times)) i
(recur (dec i) n times))))
Обратите внимание, что я заменил пустой аргумент, тело функции «конструктор по умолчанию» (p3 775147 600851475143 3) на цикл + начальное связывание. Теперь повторение связывает привязки цикла (вместо параметров fn) и переходит обратно к точке рекурсии (loop вместо fn).
Ссылка на "фантомные" вары
Я говорю о типе var, который вы можете определить с помощью REPL - во время исследовательского программирования - а затем бессознательно ссылаться на него в своем источнике. Все работает нормально, пока вы не перезагрузите пространство имен (возможно, закрыв редактор), а затем не обнаружите кучу несвязанных символов, на которые есть ссылки в вашем коде. Это также часто случается, когда вы проводите рефакторинг, перемещая переменную из одного пространства имен в другое.
Обработка понимания списка for как императива цикла for
По сути, вы создаете ленивый список на основе существующих списков, а не просто выполняете управляемый цикл. DoSq в Clojure на самом деле больше аналогичен императивным конструкциям цикла foreach.
Одним из примеров их различий является возможность фильтровать, какие элементы они перебирают, используя произвольные предикаты:
user> (for [n '(1 2 3 4) :when (even? n)] n)
(2 4)
user> (for [n '(4 3 2 1) :while (even? n)] n)
(4)
Другое отличие состоит в том, что они могут работать с бесконечными ленивыми последовательностями:
user> (take 5 (for [x (iterate inc 0) :when (> (* x x) 3)] (* 2 x)))
(4 6 8 10 12)
Они также могут обрабатывать более одного выражения привязки, сначала перебирая самое правое выражение и продвигаясь влево:
user> (for [x '(1 2 3) y '(\a \b \c)] (str x y))
("1a" "1b" "1c" "2a" "2b" "2c" "3a" "3b" "3c")
Также нет перерыва или продолжения преждевременного выхода.
Чрезмерное использование структур
У меня опыт работы с ООП, поэтому, когда я начал Clojure, мой мозг все еще думал об объектах. Я обнаружил, что моделирую все как структуру, потому что ее группировка «членов», какими бы свободными они ни были, заставляла меня чувствовать себя комфортно. На самом деле, структуры следует рассматривать как оптимизацию; Clojure поделится ключами и некоторой поисковой информацией для экономии памяти. Вы можете дополнительно оптимизировать их, указав средства доступа для ускорения процесса поиска ключей.
В целом вы ничего не получите от использования структуры поверх карты, кроме производительности, поэтому дополнительная сложность может не окупиться.
Использование несахаренных конструкторов BigDecimal
Мне нужно было много BigDecimals, и я писал уродливый код вроде этого:
(let [foo (BigDecimal. "1") bar (BigDecimal. "42.42") baz (BigDecimal. "24.24")]
когда на самом деле Clojure поддерживает литералы BigDecimal, добавляя M к числу:
(= (BigDecimal. "42.42") 42.42M)
Использование засахаренной версии избавляет от вздутия живота. В комментариях Twils упомянули, что вы также можете использовать функции bigdec и bigint, чтобы быть более явными, но при этом оставаться краткими.
Использование преобразований именования пакетов Java для пространств имен
На самом деле это не ошибка как таковая, а скорее то, что противоречит идиоматической структуре и именованию типичного проекта Clojure. В моем первом существенном проекте Clojure были объявления пространств имен и соответствующие структуры папок, например:
(ns com.14clouds.myapp.repository)
что увеличило мои полностью квалифицированные ссылки на функции:
(com.14clouds.myapp.repository/load-by-name "foo")
Чтобы еще больше усложнить ситуацию, я использовал стандартную структуру каталогов Maven :
|-- src/
| |-- main/
| | |-- java/
| | |-- clojure/
| | |-- resources/
| |-- test/
...
что более сложно, чем "стандартная" структура Clojure:
|-- src/
|-- test/
|-- resources/
это значение по умолчанию для проектов Leiningen и самого Clojure .
Карты используют Java equals (), а не Clojure = для сопоставления ключей
Изначально сообщалось chouser в IRC , такое использование Java equals () приводит к некоторым неинтуитивным результатам:
user> (= (int 1) (long 1))
true
user> ({(int 1) :found} (int 1) :not-found)
:found
user> ({(int 1) :found} (long 1) :not-found)
:not-found
Поскольку экземпляры 1 как Integer, так и Long печатаются по умолчанию одинаково, может быть сложно определить, почему ваша карта не возвращает никаких значений. Это особенно верно, когда вы передаете ключ через функцию, которая, возможно, без вашего ведома, возвращает long.
Следует отметить, что использование equals () вместо = Clojure необходимо для соответствия карт интерфейсу java.util.Map.
Я использую Programming Clojure Стюарта Хэллоуэя, Practical Clojure Люка Вандерхарта, а также помощь бесчисленных хакеров Clojure в IRC и списке рассылки, чтобы помочь в моих ответах.