Ответ 1
Буквенные символы
В какой-то момент я читал в матрице, которая использовала ведущие нули для поддержания правильных строк и столбцов. Математически это правильно, так как начальный нуль, очевидно, не меняет базового значения. Однако попытки определить var с этой матрицей таинственно исчезли:
java.lang.NumberFormatException: Invalid number: 08
что полностью сбило меня с толку. Причина в том, что Clojure обрабатывает буквальные целочисленные значения с ведущими нулями как восьмеричные, а восьмеричное число не равно 8.
Я также должен отметить, что Clojure поддерживает традиционные шестнадцатеричные значения Java через префикс 0x. Вы также можете использовать любое основание между 2 и 36, используя нотацию "base + r + value", например 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}) ; notice the lack of parenthesis
и поэтому вы не можете иметь буквальное значение ([],: a, 4,%) как тело анонимной функции.
В комментариях были даны два решения. Брайан Карпер предлагает использовать конструкторы реализации последовательностей (массив-карта, хэш-набор, вектор) так:
(#(array-map %1 %2) :a 1)
а Дэн показывает, что вы можете использовать функцию identity, чтобы развернуть внешние скобки:
(#(identity {%1 %2}) :a 1)
Предложение Брайана на самом деле приводит меня к следующей ошибке...
Думая, что hash-map или array-map определить неизменную реализацию конкретной карты
Рассмотрим следующее:
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 или conj - может принимать PersistentArrayMap и возвращать PersistentHashMap, который быстрее работает для больших карт.
Использование функции как точки рекурсии, а не цикла для обеспечения начальных привязок
Когда я начал, я написал много таких функций:
; Project Euler #3
(defn p3
([] (p3 775147 600851475143 3))
([i n times]
(if (and (divides? i n) (fast-prime? i times)) i
(recur (dec i) n times))))
Если на самом деле цикл был бы более кратким и идиоматичным для этой конкретной функции:
; Elapsed time: 387 msecs
(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))))
Обратите внимание, что я заменил пустой аргумент, конструктор "default constructor" body (p3 775147 600851475143 3) с циклом + начальное связывание. recur теперь восстанавливает привязки к циклу (вместо параметров fn) и возвращается к точке рекурсии (цикл вместо fn).
Ссылка на phantom "vars
Я говорю о типе var, который вы можете определить с помощью REPL - во время вашего поискового программирования, а затем бессознательно ссылайтесь в своем источнике. Все работает нормально, пока вы не перезагрузите пространство имен (возможно, закрыв редактор), а затем обнаружите кучу несвязанных символов, на которые ссылается весь ваш код. Это также часто происходит при рефакторинге, перемещая переменную из одного пространства имен в другое.
Рассмотрение для понимания списка как императив для цикла
По существу, вы создаете ленивый список на основе существующих списков, а не просто выполняете управляемый цикл. Clojure doseq на самом деле больше похож на императивные конструкторы цикла 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")
Там также нет break или продолжить, чтобы выйти преждевременно.
Избыточное использование структур
Я родом из OOPish, поэтому, когда я начал Clojure, мой мозг все еще думал об объектах. Я обнаружил, что моделирую все как struct, потому что его группировка "членов", как бы они ни были свободными, заставила меня чувствовать себя комфортно. На самом деле structs следует в основном рассматривать как оптимизацию; Clojure будет делиться ключами и некоторой информацией поиска для сохранения памяти. Вы также можете оптимизировать их, указав accessors, чтобы ускорить процесс поиска ключей.
В целом вы ничего не получаете от использования структуры по сравнению с картой, за исключением производительности, поэтому добавленная сложность может не стоить того.
Использование unsugared конструкторов BigDecimal
Мне понадобилось много BigDecimals и писал уродливый код следующим образом:
(let [foo (BigDecimal. "1") bar (BigDecimal. "42.42") baz (BigDecimal. "24.24")]
когда на самом деле Clojure поддерживает BigDecimal литералы, добавляя M к числу:
(= (BigDecimal. "42.42") 42.42M) ; true
Использование sugared версии вырезает много наворотов. В комментариях 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 печатаются по умолчанию одинаково, может быть сложно определить, почему ваша карта не возвращает никаких значений. Это особенно верно, когда вы передаете свой ключ через функцию, которая, возможно, без ведома вам, возвращает длинный.
Следует отметить, что использование Java equals() вместо Clojure = имеет важное значение для соответствия карт интерфейсу java.util.Map.
Я использую Программирование Clojure Стюарта Хэллоуэй, Практическое Clojure от Luke VanderHart и помощь бесчисленных хакеров Clojure на IRC и список рассылки, чтобы помочь мои ответы.