Распространенные ошибки программирования для разработчиков Clojure, чтобы избежать [закрыто]

Какие распространенные ошибки допускают разработчики Clojure и как мы можем их избежать?

Например; новички в Clojure считают, что функция contains? работает так же, как и java.util.Collection#contains. Однако contains? будет работать аналогично только при использовании с индексированными коллекциями, такими как карты и наборы, и вы ищете данный ключ:

(contains? {:a 1 :b 2} :b)
;=> true
(contains? {:a 1 :b 2} 2)
;=> false
(contains? #{:a 1 :b 2} :b)
;=> true

При использовании с нумерованными коллекциями (векторами, массивами) contains? only проверяет, является ли данный элемент в пределах допустимого диапазона индексов (начиная с нуля):

(contains? [1 2 3 4] 4)
;=> false
(contains? [1 2 3 4] 0)
;=> true

Если дан список, contains? никогда не вернет true.

92 голоса | спросил 6 revs, 5 users 74%
fogus
1 Jam1000000amThu, 01 Jan 1970 03:00:00 +030070 1970, 03:00:00

6 ответов


0

Забыть принудительное вычисление ленивых последовательностей

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

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

map никогда не оценивается, он молча отбрасывается, потому что ленив. Вы должны использовать один из doseq, 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

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

user=> (map println [:foo :bar])
(:foo
:bar
nil nil)
ответил Brian Carper 7 Jpm1000000pmThu, 07 Jan 2010 22:14:19 +030010 2010, 22:14:19
0

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

Clojure if обрабатывает логические объекты Java всегда как true, даже если его значение равно false. Так что если у вас есть функция Java-земли, которая возвращает Java-булево значение, убедитесь, что вы не проверяете ее напрямую  (if java-bool "Yes" "No") скорее (if (boolean java-bool) "Yes" "No")

Я сгорел этим с библиотекой clojure.contrib.sql, которая возвращает логические поля базы данных как логические объекты Java.

ответил Vagif Verdi 11 Jam1000000amMon, 11 Jan 2010 06:40:54 +030010 2010, 06:40:54
0

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

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

Не совсем ленивые последовательности.
Вы можете создать ленивую последовательность с помощью 'lazy-seq или 'lazy-cons (или на основе ленивых API более высокого уровня), но если вы оберните его в 'vec или передадите его через какую-то другую функцию, которая реализует последовательность, то он больше не будет ленивым. И стек, и куча могут быть переполнены этим.

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

ответил Chris Vest 7 Jpm1000000pmThu, 07 Jan 2010 18:17:48 +030010 2010, 18:17:48
0

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

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

против.

(map do-stuff data)

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

ответил Arthur Ulfeldt 7 Jpm1000000pmThu, 07 Jan 2010 21:42:25 +030010 2010, 21:42:25
0

Типы коллекций имеют различные поведения для некоторых операций:

user=> (conj '(1 2 3) 4)    
(4 1 2 3)                 ;; new element at the front
user=> (conj [1 2 3] 4) 
[1 2 3 4]                 ;; new element at the back

user=> (into '(3 4) (list 5 6 7))
(7 6 5 3 4)
user=> (into [3 4] (list 5 6 7)) 
[3 4 5 6 7]

Работа со строками может сбивать с толку (я до сих пор не совсем понял). В частности, строки не совпадают с последовательностями символов, даже если над ними работают функции последовательности:

user=> (filter #(> (int %) 96) "abcdABCDefghEFGH")
(\a \b \c \d \e \f \g \h)

Чтобы вернуть строку, вам нужно сделать следующее:

user=> (apply str (filter #(> (int %) 96) "abcdABCDefghEFGH"))
"abcdefgh"
ответил Arthur Ulfeldt 7 Jpm1000000pmThu, 07 Jan 2010 21:42:25 +030010 2010, 21:42:25
0

слишком много паратезов, особенно с вызовом метода void 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]
ответил miaubiz 8 Jpm1000000pmFri, 08 Jan 2010 13:59:09 +030010 2010, 13:59:09

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

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132