Какой способ я предпочитаю проверять, является ли объект пустым списком в Clojure? Обратите внимание, что я хочу проверить именно это, а не то, пуста ли она как последовательность. Если это "ленивый объект" (LazySeq
, Iterate
,...), я не хочу, чтобы он получал realized?
.
Ниже я привожу несколько возможных тестов для x
.
;0
(= clojure.lang.PersistentList$EmptyList (class x))
;1
(and (list? x) (empty? x))
;2
(and (list? x) (zero? (count x)))
;3
(identical? () x)
Тест 0 немного низкоуровневый и зависит от «деталей реализации». Моя первая версия была (instance? clojure.lang.PersistentList$EmptyList x)
, что дает IllegalAccessError
. Почему это так? Разве такой тест не возможен?
Тесты 1 и 2 более высокого уровня и более общие, так как list?
проверяет, реализует ли что-то IPersistentList
. Я думаю, что они также немного менее эффективны. Обратите внимание, что порядок двух подтестов важен, поскольку мы полагаемся на короткое замыкание.
Тест 3 работает в предположении, что каждый пустой список является одним и тем же объектом. Проведенные мной тесты подтверждают это предположение, но гарантируется ли оно? Даже если это так, стоит ли полагаться на этот факт?
Все это может показаться тривиальным, но я был немного озадачен, не найдя совершенно простого решения (или даже встроенной функции) для такой простой задачи.
Обновить
Возможно, я не очень хорошо сформулировал вопрос. Оглядываясь назад, я понял, что хотел проверить, является ли что-то неленивой пустой последовательностью. Наиболее важным требованием для моего варианта использования является то, что если это ленивая последовательность, она не реализуется, то есть преобразователь не выполняется принудительно.
Использование термина «список» немного сбивает с толку. Ведь что такое список? Если это что-то конкретное, например PersistentList
, то оно неленивое. Если это что-то абстрактное вроде IPersistentList
(это то, что проверяет list?
и, вероятно, правильный ответ), то отсутствие лени точно не гарантируется. Так уж получилось, что текущие ленивые типы последовательностей Clojure не реализуют этот интерфейс.
Итак, прежде всего мне нужен способ проверить, является ли что-то ленивой последовательностью. Лучшее решение, которое я могу придумать прямо сейчас, — это использовать IPending
для проверки лени в целом:
(def lazy? (partial instance? clojure.lang.IPending))
Хотя существуют некоторые типы ленивых последовательностей (например, фрагментированные последовательности, такие как Range
и LongRange
), которые не реализуют IPending
, кажется разумным ожидать, что ленивые последовательности реализуют его в целом. LazySeq
делает это, и это действительно важно в моем конкретном случае использования.
Теперь, полагаясь на короткое замыкание, чтобы предотвратить реализацию empty?
(и не дать ему неприемлемый аргумент), мы имеем:
(defn empty-eager-seq? [x] (and (not (lazy? x)) (seq? x) (empty? x)))
Или, если мы знаем, что имеем дело с последовательностями, как в моем случае, мы можем использовать менее строгие ограничения:
(defn empty-eager? [x] (and (not (lazy? x)) (empty? x)))
Конечно, мы можем написать безопасные тесты для более общих типов, таких как:
(defn empty-eager-coll? [x] (and (not (lazy? x)) (coll? x) (empty? x)))
(defn empty-eager-seqable? [x] (and (not (lazy? x)) (seqable? x) (empty? x)))
При этом рекомендуемый тест 1 также работает для моего случая благодаря сокращению и тому факту, что LazySeq
не реализует IPersistentList
. Учитывая это и то, что формулировка вопроса была неоптимальной, я приму краткий ответ Ли и поблагодарю Алана Томпсона за его время и за полезную мини-дискуссию, которую мы провели с голосованием.