Общее программирование принимает за разработчиков Clojure для предотвращения [закрытый]

92
задан 6 revs, 5 users 74% 9 February 2014 в 18:40
поделиться

7 ответов

Буквальные октябрьские

В какой-то момент я читал в матрице, которая использовала ведущие нули для поддержания правильных строк и столбцов. Математически это правильно, так как ведущие нули, очевидно, не изменяют значение, лежащее в основе. Попытки определить вариатор с этой матрицей, однако, таинственно провалились бы:

java.lang.NumberFormatException: Invalid number: 08

, что меня полностью озадачило. Причина в том, что Клоюре трактует буквальные целые значения с ведущими нулями как восьмеричные, а в восьмеричной нет числа 08.

Следует также отметить, что Клоюре поддерживает традиционные яванские шестнадцатеричные значения с помощью префикса 0x. Вы также можете использовать любые базы от 2 до 36, используя нотацию "base+r+value", например 2r101010 или 36r16, которые являются 42 базовыми десятью.


Пытаясь вернуть литералы в анонимной функции literal

Это работает:

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)

а Dan показывает, что можно использовать функцию identity для разворачивания внешней скобки:

(#(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

Хотя в целом вам не придется беспокоиться о конкретной реализации карты Клоюра, вы должны знать, что функции, которые выращивают карту - такие как assoc или conj - могут взять PersistentArrayMap и вернуть PersistentHashMap, которая работает быстрее при работе с большими картами.


Используя функцию в качестве точки рекурсии, а не цикл loop для обеспечения начальной привязки

Когда я начинал, я написал много таких функций:

; 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 rebindsing the loop bindings (вместо параметров fn) и jump back to the recursion point (цикл, вместо fn).


Referencing "phantom" vars

Я говорю о типе var, который вы можете определить, используя REPL - во время вашего пробного программирования - затем неосознанно ссылаясь в исходнике. Всё работает нормально до тех пор, пока вы не перезагрузите пространство имён (возможно, закрыв свой редактор), а затем не обнаружите кучу несвязанных символов, на которые ссылаются по всему вашему коду. Это также часто происходит, когда вы рефакторингуете, перемещая вариары из одного пространства имен в другое.


Обращаясь к пониманию списка для как к императиву для цикла

По сути, вы создаёте ленивый список, основанный на существующих списках, а не просто выполняете управляемый цикл. Доза Кложура на самом деле больше похожа на императивные конструкции петли фораха.

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

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 или continue, чтобы преждевременно выйти из него.


Чрезмерное использование структур

Я пришел из OOPish фона, так что когда я начал Clojure мой мозг все еще думал в терминах объектов. Я обнаружил, что моделирую все как структуру , потому что ее группировка "членов", какой бы свободной она ни была, заставляла меня чувствовать себя комфортно. На самом деле structs в основном следует рассматривать как оптимизацию; Clojure будет делиться ключами и некоторой информацией о поиске для сохранения памяти. В дальнейшем их можно оптимизировать, определив аксессуары для ускорения процесса поиска ключей.

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


Используя необязательные 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's 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

Поскольку оба экземпляра Integer и Long по умолчанию распечатаны одинаково, может быть трудно определить, почему Ваша карта не возвращает никаких значений. Это особенно верно, когда Вы передаете свой ключ через функцию, которая, возможно, неизвестно Вам, возвращает long.

Следует отметить, что использование Java's equals() вместо Clojure's = необходимо для того, чтобы карты соответствовали java. use.map interface.


Я использую Программирование Clojure Стюарта Халлоуэя, Практическую клоюре Люка ВандерХарта, а также помощь бесчисленного множества хакеров Clojure на IRC и список рассылки, чтобы помочь мне с ответами на вопросы.

70
ответ дан 24 November 2019 в 06:31
поделиться

Я - ноб клоюра. У более продвинутых пользователей могут возникнуть более интересные проблемы.

пытаясь распечатать бесконечные ленивые последовательности.

Я знал, что я делаю со своими ленивыми последовательностями, но в целях отладки я вставил несколько вызовов печати/prn/pr, временно забыв, что я печатаю. Забавно, почему мой компьютер завис?

пытается запрограммировать Clojure в обязательном порядке.

Есть искушение создать целый ряд ref s или atom s и написать код, который постоянно портится с их состоянием. Это можно сделать, но это не очень подходит. Он также может иметь плохую производительность, и редко извлекать выгоду из нескольких ядер.

пытается программировать Clojure на 100% функционально.

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

пытается сделать слишком много на Java.

Поскольку к Java так легко добраться, иногда возникает соблазн использовать Clojure в качестве обёртки языка сценариев вокруг Java. Конечно, вам нужно будет сделать именно это при использовании функциональности библиотеки Java, но нет смысла (например) поддерживать структуры данных на Java или использовать типы данных на Java, такие как коллекции, для которых есть хорошие эквиваленты в Clojure.

20
ответ дан 24 November 2019 в 06:31
поделиться

Забудьте о принудительной оценке ленивых seqs

Ленивые seqs не оцениваются, если вы не попросите их оценить. Вы можете ожидать, что это что-то напечатает, но это не так.

user=> (defn foo [] (map println [:foo :bar]) nil)
#'user/foo
user=> (foo)
nil

Карта map никогда не вычисляется, она бесшумно отбрасывается, потому что она ленивая. Вы должны использовать одну из дозq, dorun, doall и т.д., чтобы заставить оценить ленивые последовательности на наличие побочных эффектов.

user=> (defn foo [] (doseq [x [:foo :bar]] (println x)) nil)
#'user/foo
user=> (foo)
:foo
:bar
nil
user=> (defn foo [] (dorun (map println [:foo :bar])) nil)
#'user/foo
user=> (foo)
:foo
:bar
nil

Использование пустой карты в виде REPL выглядит так, как будто это работает, но это работает только потому, что REPL заставляет оценивать ленивые последовательности для самих ленивых последовательностей. Это может сделать ошибку еще более заметной, потому что ваш код работает в REPL и не работает ни из исходного файла, ни внутри функции.

user=> (map println [:foo :bar])
(:foo
:bar
nil nil)
42
ответ дан 24 November 2019 в 06:31
поделиться

Много вещей уже упоминалось. Я просто добавлю еще один.

Clojure Если лечит , лечит Java Boolean Objects всегда так же верно, даже если это значение, является ложным. Так что, если у вас есть функция земли Java, которая возвращает логическое значение Java, убедитесь, что вы не проверяете его напрямую (Если Java-Bool «Да» «Нет») скорее (если (Boolean Java-Bool) «Да» «Нет») .

Я был сожжен этим с помощью библиотеки CLOJURE.CONTRIB.SQL, которая возвращает базу данных логических полей как Java Boolean Objects.

13
ответ дан 24 November 2019 в 06:31
поделиться

Держите голову в цикле.
Вы рискуете остаться без памяти, если зацикливаете элементы потенциально очень большой или бесконечной ленивой последовательности, сохраняя ссылку на первый элемент.

Забудьте об отсутствии ТСО.
Регулярные хвостовые вызовы потребляют пространство стека, и они будут переполнены, если вы не будете осторожны. Клоюр имеет 'recur и 'trampoline для обработки многих случаев, когда оптимизированные хвостовые вызовы будут использоваться в других языках, но эти методы должны быть намеренно применены.

Not-quite-lazy последовательности.
Можно построить ленивую последовательность с помощью 'lazy-seq или 'lazy-cons (или построив ее на API более высокого уровня), но если вы обернете ее в 'vec или пропустите через какую-нибудь другую функцию, реализующую последовательность, то она больше не будет ленивой. Этим можно переполнить и стек, и кучу.

Поместив мутируемые вещи в ссылки.
Технически это можно сделать, но только ссылка на объект в самом ссылке регулируется STM - а не ссылочным объектом и его полями (если только они не являются неизменяемыми и не указывают на другие ссылки). Поэтому по возможности предпочитайте в ссылках только неизменяемые объекты. То же самое касается и атомов

.
13
ответ дан 24 November 2019 в 06:31
поделиться

слишком много парантезов, особенно с вызовом пустого java-метода, внутри которого результат NPE:

public void foo() {}

((.foo))

приводит к NPE из внешних парантезов, так как внутренние парантезы оцениваются до нуля.

public int bar() { return 5; }

((.bar)) 

приводит к тому, что легче отлаживать:

java.lang.Integer cannot be cast to clojure.lang.IFn
  [Thrown class java.lang.ClassCastException]
3
ответ дан 24 November 2019 в 06:31
поделиться

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

(defn work [data]
    (do-stuff (first data))
    (recur (rest data)))

vs.

(map do-stuff data)

Функция map (в последней ветке) использует кусочки последовательностей и многие другие оптимизации. Также, так как эта функция часто выполняется, Hotspot JIT обычно оптимизирован и готов к работе с любым "временем разогрева".

9
ответ дан 24 November 2019 в 06:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: