Clojure: Идиоматический способ вызова содержит? на ленивой последовательности

Существует ли идиоматический способ определения того, содержит ли LazySeq элемент? Начиная с Clojure 1.5 вызов contains? вызывает исключение IllegalArgumentException:

IllegalArgumentException contains? not supported on type: clojure.lang.LazySeq      
clojure.lang.RT.contains (RT.java:724)

До 1.5, насколько я знаю, он всегда возвращал false.

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

Я придумал следующее:

(defn lazy-contains? [col key]
  (not (empty? (filter #(= key %) col))))

Но это не совсем правильно. Есть ли лучший способ?

Ответ 1

Во-первых, ленивые секвенции не эффективны для проверки членства. Рассмотрите возможность использования набора вместо ленивого seq.

Если набор непрактичен, ваше решение неплохое. Несколько возможных улучшений:

  • "Не пустой" немного неудобно. Просто использовать seq достаточно, чтобы получить значение nil-or-truthy, которое ваши пользователи могут использовать в if.You может обернуть это в boolean, если вы хотите true или false.

  • Поскольку вы только заботитесь о первом совпадении, вы можете использовать некоторые вместо фильтра и seq.

  • Удобный способ написать предикат равенства - это литеральный набор, такой как # {key}, хотя если ключ равен нулю, он всегда будет возвращать нуль, независимо от того, найден ли nil наш.

Все вместе, что дает вам:

(defn lazy-contains? [col key]
  (some #{key} col))

Ответ 2

Если вы используете some вместо filter, как в вашем примере, вы получите немедленное возвращение, как только будет найдено значение, а не для принудительной оценки всей последовательности.

(defn lazy-contains? [coll key]
  (boolean (some #(= % key) coll)))

Изменить. Если вы не принуждаете результат к логическому, обратите внимание, что вместо false вы получите nil, если ключ не найден.