Буквальні октали
Одного разу я читав матрицю, яка використовувала провідні нулі для підтримки належних рядків і стовпців. Математично це правильно, оскільки нуль, очевидно, не змінює базового значення. Спроби визначити var за допомогою цієї матриці, загадково не вдалися б:
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}) ; notice the lack of parenthesis
і тому ви не можете мати будь-яке буквальне значення ([],: 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 або 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))))
Зверніть увагу, що я замінив порожній аргумент, тіло функції "конструктор за замовчуванням" (p3 775147 600851475143 3) на цикл + початкове прив'язку. RECUR Тепер виконує повторну прив'язку прив'язок контуру (замість параметрів Fn) і переходить назад до точки рекурсії (петлі, замість Fn).
Посилання на "фантом" вар
Я говорю про тип var, який ви можете визначити, використовуючи REPL - під час вашого дослідницького програмування - тоді несвідомо посилаючись у своєму джерелі. Все працює нормально, доки ви не перезавантажите простір імен (можливо, закривши редактор) і пізніше не виявите купу незв’язаних символів, на які посилається весь код. Це також часто трапляється, коли ви здійснюєте рефакторинг, переміщуючи var з одного простору імен в інший.
Ставлення до розуміння списку як до імперативу циклу
По суті, ви створюєте лінивий список на основі існуючих списків, а не просто виконуєте керований цикл. 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")
Також немає перерви або продовжуйте передчасно виїжджати.
Надмірне використання конструкцій
Я походжу з OOPish, тому, коли я починав роботу з Clojure, мій мозок все ще мислив з точки зору предметів. Я виявив, що моделюю все як структуру, тому що його угруповання "членів", хоч би і вільним, дало мені почуття комфорту. Насправді, структури переважно слід вважати оптимізацією; Clojure надасть ключі та деяку інформацію про пошук для збереження пам’яті. Ви можете ще більше оптимізувати їх, визначаючи аксессор для прискорення ключового процесу пошуку.
Загалом ви не отримуєте нічого від використання структури над картою, окрім продуктивності, тому додаткова складність може бути не варта того.
Використання нецугованих конструкторів BigDecimal
Мені потрібно було багато BigDecimals і я писав некрасивий код так:
(let [foo (BigDecimal. "1") bar (BigDecimal. "42.42") baz (BigDecimal. "24.24")]
коли насправді Clojure підтримує літерали BigDecimal, додаючи M до числа:
(= (BigDecimal. "42.42") 42.42M) ; true
Використання зацукрованої версії вирізує багато здуття. У коментарях 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/
що є типовим для проектів Лейнінгена та самого 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 друкуються однаково за замовчуванням, може бути важко виявити, чому ваша карта не повертає жодних значень. Це особливо вірно, коли ви передаєте ключ через функцію, яка, можливо, невідома для вас, повертає long.
Слід зазначити, що використання Java equals () замість Clojure's = важливо для того, щоб карти відповідали інтерфейсу java.util.Map.
Я використовую програмування Clojure Стюарта Хеллоуея, Practical Clojure Люка ВандерХарта та допомогу незліченних хакерів Clojure у IRC та списку розсилки, щоб допомогти у відповіді на мої відповіді.