#exception #clojure #nullpointerexception
#исключение #clojure #исключение nullpointerexception
Вопрос:
Я новичок в clojure и пытаюсь написать простую функцию, которая получает список чисел и фильтрует только четные числа.
Я хочу сделать это без фильтра или даже?, только чистый clojure
(defn my-even [ilist]
(if
(= (mod (first ilist) 2) 0)
(concat (list (first ilist)) (my-even (rest ilist)))
(my-even (rest ilist))
)
)
Я пытаюсь запустить его:
(my-even '(1,2,3,4,5))
Но получаю ошибку:
#<CompilerException java.lang.NullPointerException (NO_SOURCE_FILE:0)>
Что не так?
Спасибо.
Комментарии:
1. Извините, я отредактировал, это не тот код. только мой-четный
Ответ №1:
Как сказал Джонас, у вас нет базового варианта; в дополнение к этому, не является идиоматическим Clojure (или любым другим Lisp) помещать скобки в отдельные строки, а также сохранять предикат if
‘s в той же строке.
С деструктурированием это немного более читабельно:
(defn my-even? [coll]
(if-let [[first amp; rest] coll]
(if (= (mod first 2) 0)
(cons first (my-even? rest))
(my-even? rest))))
Комментарии:
1. Вам действительно не стоит проверять
coll
, но(seq coll)
. В большинстве случаев это не имеет значения, но попробуйте(my-even? (range 0))
.
Ответ №2:
Ваша рекурсивная функция my-even
не имеет базового варианта. Что происходит, когда в списке больше нет элементов? (first ilist)
возвращает nil
и (mod nil 2)
выдает исключение NullPointerException.
Вы должны каким-то образом проверить наличие пустого списка.
Ответ №3:
Приятно видеть, что так много людей изучают Clojure на этой неделе 🙂 начинать с такой фундаментальной проблемы, как эта, — действительно хорошее начало. Ответы Хамзы и Джонаса достаточно хорошо отражают исходный вопрос. Я хотел бы предложить несколько незапрошенных советов о том, где его взять отсюда, в надежде, что это будет полезно.
Как только у вас есть базовая рекурсивная форма, вы можете превратить ее в идиоматическую Clojure, как правило, с помощью:
1) используйте хвостовые рекурсивные формы, когда можете (вы уже это делали)
2) замените прямую рекурсию recur
вызовом, чтобы не допустить переполнения стека. (начиная с рабочего ответа Хамзы)
(defn my-even? [coll]
(if-let [[first amp; rest] coll]
(if (= (mod first 2) 0)
(cons first (my-even? rest))
(recur rest))))
это recur
приводит к тому, что компилятор переходит к началу кадра стека вместо выделения нового. без этого он взорвет стек.
3) во многих случаях вы можете устранить (defn [] ... (recur))
шаблон с помощью функции более высокого порядка, такой как map
, reduce
, filter
, и т.д. for
В этом упражнении я вижу, что вы пытаетесь не использовать filter
или even
, поэтому, очевидно, вы могли бы написать my-filter и my-even , и это было бы нормально 😉
4) извлеките разделяемые части (построение списка, выбор того, что включить) в повторно используемые функции и загрузите любые, которые обычно полезны для проекта clojure contrib 🙂
5) подумайте очень тщательно, если вы обнаружите, что используете (lazy-seq ...)
, поскольку есть большая вероятность, что вы заново изобретаете колесо.
Ответ №4:
Вот еще одно решение, которое не требует деструктурирования, только базовые функции lisp и scheme.
(defn my-even [ilist]
(cond (empty? ilist) '() ;; base case
(= (mod (first ilist) 2) 0)
(cons (first ilist) (my-even (next ilist)))
:else (my-even (next ilist))))
Комментарии:
1. используйте recur вместо my-даже в последних двух строках, иначе он получит хорошее исключение StackOverflowException
2. @Arthur Только если список «длинный». Это правда, для кода упражнения, не требующего обучения, вам нужно будет использовать recur для оптимизации конечного вызова, чтобы не использовать слишком много кадров стека.