Ошибка исключения Clojure NullPointerException

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