Ответ 1
(conj collection item)
добавляет item
в collection
. Для этого нужно реализовать collection
. (Я объясню, почему ниже.) Таким образом, рекурсивный вызов происходит немедленно, а не откладывается.
(cons item collection)
создает последовательность, начинающуюся с item
, а затем все в collection
. Примечательно, что ему не нужно понимать collection
. Таким образом, рекурсивный вызов будет отложен (из-за использования lazy-seq
), пока кто-то не попытается получить хвост результирующей последовательности.
Я объясню, как это работает внутри:
cons
фактически возвращает объект clojure.lang.Cons
, из которого сделаны ленивые последовательности. conj
возвращает тот же тип коллекции, который вы передаете (будь то список, вектор или что-то еще). conj
делает это с использованием полиморфного вызова метода Java в самой коллекции. (См. строка 524 из clojure/src/jvm/clojure/lang/RT.java
.)
Что происходит, когда вызов метода Java происходит в объекте clojure.lang.LazySeq
, который возвращается lazy-seq
? (Как cons
и LazySeq
объекты работают вместе, чтобы сформировать ленивые последовательности станут яснее ниже.) Посмотрите строка 98 из clojure/src/jvm/clojure/lang/LazySeq.java
. Обратите внимание, что он вызывает метод с именем seq
. Это то, что реализует значение LazySeq
(переход к ISeq.
Обратите внимание, что cons
объекты в Clojure отличаются от "cons cells" в других Lisps - в большинстве Lisps "cons" - это просто объект, который содержит 2 указателя на другие произвольные объекты. Таким образом, вы можете использовать cons-клетки для создания деревьев и т.д. A Clojure cons
принимает произвольную Object
как head, а ISeq
- как хвост. Поскольку cons
сам реализует ISeq
, вы можете создавать последовательности из cons
объектов, но они могут точно также указывать на векторы или списки и т.д. (Обратите внимание, что "список" в Clojure является специальным type (PersistentList
) и не построен из объектов cons
.) clojure.lang.LazySeq
также реализует ISeq
, поэтому его можно использовать как хвост ( "cdr" в Lisps) cons
. A LazySeq
содержит ссылку на некоторый код, который оценивается как ISeq
какого-либо типа, но фактически не оценивает этот код до тех пор, пока он не понадобится, и после того, как он оценит код, он кэширует возвращенный ISeq
и делегирует к нему.
... это все начинает иметь смысл? Вы понимаете, как работают ленивые последовательности? В основном, вы начинаете с LazySeq
. Когда реализуется LazySeq
, он вычисляет a cons
, который указывает на другой LazySeq
. Когда этот реализуется... вы получаете идею. Таким образом, вы получаете цепочку из LazySeq
объектов, каждая из которых удерживает (и делегирует) a cons
.
О разнице между "conses" и "lists" в Clojure, "lists" (PersistentList
objects) содержит кэшированное поле "length", поэтому они могут отвечать на count
в O (1) раз, Это не будет работать в других Lisp, потому что в большинстве Lisps "списки" изменяемы. Но в Clojure они неизменяемы, поэтому кэширование длины работает.
cons
объекты в Clojure не имеют кешированной длины - если да, то как их можно использовать для реализации ленивых (и даже бесконечных) последовательностей? Если вы попытаетесь взять count
для cons
, он просто вызывает count
на своем хвосте и затем увеличивает результат на 1.