Skip to content

Commit c327e16

Browse files
authored
Lazy sequences and tests (#118)
1 parent c7a6027 commit c327e16

File tree

2 files changed

+120
-1
lines changed

2 files changed

+120
-1
lines changed

basilisp/lang/seq.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from abc import ABC, abstractmethod
2-
from typing import Iterator, Optional, TypeVar, Iterable, Any
2+
from typing import Iterator, Optional, TypeVar, Iterable, Any, Callable
33

44
from basilisp.lang.meta import Meta
55
from basilisp.lang.util import lrepr
@@ -112,6 +112,70 @@ def cons(self, elem):
112112
return Cons(elem, self)
113113

114114

115+
class LazySeq(Seq[T]):
116+
"""LazySeqs are wrappers for delaying sequence computation. Create a LazySeq
117+
with a function that can either return None or a Seq. If a Seq is returned,
118+
the LazySeq is a proxy to that Seq."""
119+
__slots__ = ('_gen', '_realized', '_seq')
120+
121+
def __init__(self, gen: Callable[[], Optional[Seq]]) -> None:
122+
self._gen = gen # pylint:disable=assigning-non-slot
123+
self._realized = False # pylint:disable=assigning-non-slot
124+
self._seq: Optional[Seq] = None # pylint:disable=assigning-non-slot
125+
126+
def _realize(self):
127+
if not self._realized:
128+
self._seq = self._gen() # pylint:disable=assigning-non-slot
129+
self._realized = True # pylint:disable=assigning-non-slot
130+
131+
@property
132+
def is_empty(self) -> bool:
133+
if not self._realized:
134+
return False
135+
if self._seq is None:
136+
return True
137+
if self._seq.first is None and self._seq.rest == empty():
138+
return True
139+
if isinstance(self._seq, LazySeq) and self._seq.is_empty:
140+
return True
141+
return False
142+
143+
@property
144+
def first(self) -> T:
145+
if not self._realized:
146+
self._realize()
147+
try:
148+
return self._seq.first # type: ignore
149+
except AttributeError:
150+
return None # type: ignore
151+
152+
@property
153+
def rest(self) -> Optional["Seq[T]"]:
154+
if not self._realized:
155+
self._realize()
156+
try:
157+
return self._seq.rest # type: ignore
158+
except AttributeError:
159+
return empty()
160+
161+
def cons(self, elem):
162+
return Cons(elem, self)
163+
164+
@property
165+
def realized(self):
166+
return self._realized
167+
168+
def __iter__(self):
169+
o = self
170+
while o:
171+
first = o.first
172+
if isinstance(o, LazySeq):
173+
if o.is_empty:
174+
return
175+
yield first
176+
o = o.rest
177+
178+
115179
class _EmptySequence(Seq[T]):
116180
def __repr__(self):
117181
return '()'

tests/seq_test.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,61 @@ def test_to_sequence():
88
assert llist.l(1, 2, 3) == lseq.sequence([1, 2, 3])
99

1010

11+
def test_lazy_sequence():
12+
s = lseq.LazySeq(lambda: None)
13+
assert not s.is_empty, "LazySeq has not been realized yet"
14+
assert None is s.first
15+
assert lseq.empty() == s.rest
16+
assert s.realized
17+
assert s.is_empty, "LazySeq has been realized and is empty"
18+
19+
s = lseq.LazySeq(lambda: lseq.empty())
20+
assert not s.is_empty, "LazySeq has not been realized yet"
21+
assert None is s.first
22+
assert lseq.empty() == s.rest
23+
assert s.realized
24+
assert s.is_empty, "LazySeq has been realized and is empty"
25+
26+
s = lseq.LazySeq(lambda: lseq.sequence([1]))
27+
assert not s.is_empty, "LazySeq has not been realized yet"
28+
assert 1 == s.first
29+
assert lseq.empty() == s.rest
30+
assert s.realized
31+
assert not s.is_empty, "LazySeq has been realized and is not empty"
32+
33+
def lazy_seq():
34+
def inner_seq():
35+
def inner_inner_seq():
36+
return lseq.sequence([3])
37+
38+
return lseq.LazySeq(inner_inner_seq).cons(2)
39+
40+
return lseq.LazySeq(inner_seq).cons(1)
41+
42+
s = lseq.LazySeq(lazy_seq)
43+
assert not s.is_empty, "LazySeq has not been realized yet"
44+
assert 1 == s.first
45+
assert isinstance(s.rest, lseq.LazySeq)
46+
assert s.realized
47+
assert not s.is_empty, "LazySeq has been realized and is not empty"
48+
49+
r = s.rest
50+
assert not r.is_empty, "LazySeq has not been realized yet"
51+
assert 2 == r.first
52+
assert isinstance(r.rest, lseq.LazySeq)
53+
assert r.realized
54+
assert not r.is_empty, "LazySeq has been realized and is not empty"
55+
56+
t = r.rest
57+
assert not t.is_empty, "LazySeq has not been realized yet"
58+
assert 3 == t.first
59+
assert lseq.empty() == t.rest
60+
assert t.realized
61+
assert not t.is_empty, "LazySeq has been realized and is not empty"
62+
63+
assert [1, 2, 3] == [e for e in s]
64+
65+
1166
def test_empty_sequence():
1267
empty = lseq.sequence([])
1368
assert None is empty.first

0 commit comments

Comments
 (0)