Skip to content

Commit 27e0b4d

Browse files
authored
Make lazy sequences collections. (#953)
- `ISeq` now inherits from `IPersistentCollection` so that `coll?`, `empty`, and `conj` now work for sequences. - Added trivial arities for `conj` as in Clojure These changes match the behaviour of Clojure types and functions
1 parent 8fdb81b commit 27e0b4d

File tree

5 files changed

+54
-12
lines changed

5 files changed

+54
-12
lines changed

CHANGELOG.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
### Added
99
* Added `:end-line` and `:end-col` metadata to forms during compilation (#903)
1010
* Added `basilisp.repl/source` to allow inspecting source code from the REPL (#205)
11+
* Added `conj` 1 and 0 arities (#954)
1112

1213
### Changed
1314
* Updated dozens of type annotations in the compiler to satisfy MyPy 1.11 (#910)
1415
* Update the `StreamReader` methods to stop using the term "token" to refer to individual UTF-8 characters (#915)
1516
* Update the list of Python dunder methods which are allowed to be implemented for all `deftype*` and `reify*` types (#943)
17+
* ISeq now inherits from IPersistentCollection so `coll?`, `empty`, and `conj` can now be used with sequences (#954)
1618

1719
### Fixed
1820
* Fix a bug where `.` characters were not allowed in keyword names (#899)
@@ -61,7 +63,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
6163
* Fix a bug where `basilisp.lang.compiler.exception.CompilerException` would nearly always suppress line information in it's `data` map (#845)
6264
* Fix a bug where the function returned by `partial` retained the meta, arities, and `with_meta` method of the wrapped function rather than creating new ones (#847)
6365
* Fix a bug where exceptions arising while reading reader conditional forms did not include line and column information (#854)
64-
* Fix a bug where names `def`'ed without reader metadata would cause the compiler to throw an exception (#850)
66+
* Fix a bug where names `def`'ed without reader metadata would cause the compiler to throw an exception (#850)
6567
* Fix an issue where `concat` on maps was iterating over the keys instead of the key/value pairs (#871)
6668
* Fix a bug where the compiler would throw an exception partially macroexpanding forms with `recur` forms provided as arguments (#856)
6769
* Fix a bug where the original `(var ...)` form is not retained during analysis, causing it to be lost in calls to `macroexpand` (#888)
@@ -103,7 +105,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
103105
* Fix issue with keywords not testing for membership in sets when used as a function (#762)
104106
* Fix an issue for executing Basilisp scripts via a shebang where certain platforms may not support more than one argument in the shebang line (#764)
105107
* Fix issue with keywords throwing `TypeError` when used as a function on vectors (#770)
106-
* Fix an issue where the constructors of types created by `deftype` and `defrecord` could not be called if they contained `-` characters (#777)
108+
* Fix an issue where the constructors of types created by `deftype` and `defrecord` could not be called if they contained `-` characters (#777)
107109
* Fix issue with the variadic ampersand operator treated as a binding in macros (#772)
108110
* Fix a bug the variadic arg symbol was not correctly bound to `nil` when no variadic arguments were provided (#801)
109111
* Fix a bug where the quotient of very large numbers was incorrect (#822)
@@ -473,7 +475,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
473475
* Fixed a bug where comment literals were not be fully removed from reader outputs (#196)
474476
* Fixed a bug where comment literals caused syntax errors inside collection literals (#196)
475477
* Imported namespaces no longer create extra namespaces bound to munged Python module names (#216)
476-
* Fixed a bug where `import*`s would not work within other forms
478+
* Fixed a bug where `import*`s would not work within other forms
477479
* Fixed a bug where the Basilisp import hook could be added to `sys.meta_path` multiple times (#213)
478480
* Fixed a bug where keywords could not be used in function position (#174)
479481
* Fixed a bug where macro symbols were not resolved using the same heuristics as other symbols (#183)

src/basilisp/core.lpy

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,9 +299,11 @@
299299
different positions depending on the type of ``coll``\\. ``conj`` returns
300300
the same type as ``coll``\\. If ``coll`` is ``nil``\\, return a list with
301301
``xs`` conjoined."
302-
:arglists '([coll x] [coll x & xs])}
302+
:arglists '([] [coll] [coll x] [coll x & xs])}
303303
conj
304304
(fn conj
305+
([] [])
306+
([coll] coll)
305307
([coll x]
306308
(basilisp.lang.runtime/conj coll x))
307309
([coll x & xs]

src/basilisp/lang/interfaces.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -614,7 +614,7 @@ def seq_equals(s1: Union["ISeq", ISequential], s2: Any) -> bool:
614614
return True
615615

616616

617-
class ISeq(ILispObject, ISeqable[T]):
617+
class ISeq(ILispObject, IPersistentCollection[T]):
618618
"""``ISeq`` types represent a potentially infinite sequence of elements.
619619
620620
.. seealso::
@@ -667,7 +667,7 @@ def rest(self) -> "ISeq[T]":
667667
raise NotImplementedError()
668668

669669
@abstractmethod
670-
def cons(self, elem: T) -> "ISeq[T]":
670+
def cons(self, *elem: T) -> "ISeq[T]":
671671
raise NotImplementedError()
672672

673673
def seq(self) -> "Optional[ISeq[T]]":

src/basilisp/lang/seq.py

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,15 @@ def first(self) -> Optional[T]:
4848
def rest(self) -> ISeq[T]:
4949
return self
5050

51-
def cons(self, elem: T) -> ISeq[T]:
52-
return Cons(elem, self)
51+
def cons(self, *elems: T) -> ISeq[T]: # type: ignore[override]
52+
l: ISeq = self
53+
for elem in elems:
54+
l = Cons(elem, l)
55+
return l
56+
57+
@staticmethod
58+
def empty():
59+
return EMPTY
5360

5461

5562
EMPTY: ISeq = _EmptySequence()
@@ -80,8 +87,15 @@ def first(self) -> Optional[T]:
8087
def rest(self) -> ISeq[T]:
8188
return self._rest
8289

83-
def cons(self, elem: T) -> "Cons[T]":
84-
return Cons(elem, self)
90+
def cons(self, *elems: T) -> "Cons[T]":
91+
l = self
92+
for elem in elems:
93+
l = Cons(elem, l)
94+
return l
95+
96+
@staticmethod
97+
def empty():
98+
return EMPTY
8599

86100
@property
87101
def meta(self) -> Optional[IPersistentMap]:
@@ -192,14 +206,21 @@ def rest(self) -> "ISeq[T]":
192206
except AttributeError:
193207
return EMPTY
194208

195-
def cons(self, elem):
196-
return Cons(elem, self)
209+
def cons(self, *elems: T) -> ISeq[T]: # type: ignore[override]
210+
l: ISeq = self
211+
for elem in elems:
212+
l = Cons(elem, l)
213+
return l
197214

198215
@property
199216
def is_realized(self):
200217
with self._lock:
201218
return self._gen is None
202219

220+
@staticmethod
221+
def empty():
222+
return EMPTY
223+
203224

204225
def sequence(s: Iterable[T]) -> ISeq[T]:
205226
"""Create a Sequence from Iterable s."""

tests/basilisp/test_core_fns.lpy

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,23 @@
656656
(iterate inc 2)))
657657

658658
(deftest lazy-seq-test
659+
(testing "is collection"
660+
(are [x] (coll? x)
661+
(lazy-seq)
662+
(empty (lazy-seq))
663+
(cons nil (lazy-seq))
664+
(cons nil (empty (lazy-seq)))
665+
(empty (cons nil (lazy-seq)))))
666+
667+
(testing "conj"
668+
(are [expected form] (= expected form)
669+
'() (lazy-seq)
670+
'() (conj (lazy-seq))
671+
'(0 1 2) (conj (lazy-seq '(2)) 1 0)
672+
'(0 1 2) (conj (conj (lazy-seq '(2)) 1) 0)
673+
'(1 2 3) (conj (lazy-seq) 3 2 1)
674+
'(0 1 2 3 4) (conj (lazy-seq '(1 2 3 4)) 0)))
675+
659676
(testing "corecursion"
660677
;; here we're just generally checking that we don't blow the stack, so
661678
;; 50 is an arbitrarily chosen limit which hopefully tests that adequately

0 commit comments

Comments
 (0)