Skip to content

Commit 466c861

Browse files
authored
Use an empty sequence object as a sentinel (#135)
* Use an empty sequence object as a sentinel * Add is_empty property to Seqs * Add additional assertions to seq tests * Small EmptySequence change
1 parent b88f512 commit 466c861

File tree

6 files changed

+107
-68
lines changed

6 files changed

+107
-68
lines changed

basilisp/compiler.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ def _do_ast(ctx: CompilerContext, form: llist.List) -> ASTStream:
508508

509509

510510
def _fn_args_body(ctx: CompilerContext, arg_vec: vec.Vector, # pylint:disable=too-many-locals
511-
body_exprs: llist.List) -> FunctionDefDetails:
511+
body_exprs: lseq.Seq) -> FunctionDefDetails:
512512
"""Generate the Python AST Nodes for a Lisp function argument vector
513513
and body expressions. Return a tuple of arg nodes and body AST nodes."""
514514
st = ctx.symbol_table
@@ -823,7 +823,10 @@ def __f_68(*multi_arity_args):
823823
kw_defaults=[]),
824824
body=[_compose_ifs(if_stmts),
825825
ast.Raise(exc=ast.Call(func=_load_attr('basilisp.lang.runtime.RuntimeException'),
826-
args=[ast.Str(f"Wrong number of args passed to function: {name}")],
826+
args=[ast.Str(f"Wrong number of args passed to function: {name}"),
827+
ast.Call(func=ast.Name(id='len', ctx=ast.Load()),
828+
args=[ast.Name(id=_MULTI_ARITY_ARG_NAME, ctx=ast.Load())],
829+
keywords=[])],
827830
keywords=[]),
828831
cause=None)],
829832
decorator_list=[],
@@ -1042,7 +1045,7 @@ def let_32(a_33, b_34, c_35):
10421045
# Generate a function to hold the body of the let expression
10431046
letname = genname('let')
10441047
with ctx.new_symbol_table(letname):
1045-
args, body, vargs = _fn_args_body(ctx, vec.vector(arg_syms.keys()), form[2:])
1048+
args, body, vargs = _fn_args_body(ctx, vec.vector(arg_syms.keys()), runtime.nthrest(form, 2))
10461049
yield _dependency(_expressionize(body, letname, args=args, vargs=vargs))
10471050

10481051
# Generate local variable assignments for processing let bindings

basilisp/lang/list.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
from pyrsistent import plist, PList
2+
from pyrsistent._plist import _EMPTY_PLIST
23

34
from basilisp.lang.collection import Collection
45
from basilisp.lang.meta import Meta
5-
from basilisp.lang.seq import Seq
6+
from basilisp.lang.seq import Seq, EMPTY
67
from basilisp.lang.util import lrepr
78

89

@@ -43,6 +44,10 @@ def with_meta(self, meta) -> "List":
4344
meta)
4445
return list(self._inner, meta=new_meta)
4546

47+
@property
48+
def is_empty(self):
49+
return self._inner is _EMPTY_PLIST
50+
4651
@property
4752
def first(self):
4853
try:
@@ -51,7 +56,9 @@ def first(self):
5156
return None
5257

5358
@property
54-
def rest(self) -> "List":
59+
def rest(self) -> Seq:
60+
if self._inner.rest is _EMPTY_PLIST:
61+
return EMPTY
5562
return List(self._inner.rest)
5663

5764
def cons(self, *elems) -> "List":

basilisp/lang/runtime.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -368,12 +368,12 @@ def rest(o) -> Optional[lseq.Seq]:
368368
if isinstance(o, lseq.Seq):
369369
s = o.rest
370370
if s is None:
371-
return lseq.empty()
371+
return lseq.EMPTY
372372
return s
373-
s = to_seq(o)
374-
if s is None:
375-
return lseq.empty()
376-
return s.rest
373+
n = to_seq(o)
374+
if n is None:
375+
return lseq.EMPTY
376+
return n.rest
377377

378378

379379
def nthrest(coll, i: int):
@@ -420,7 +420,7 @@ def cons(o, seq) -> lseq.Seq:
420420

421421
def _seq_or_nil(s: lseq.Seq) -> Optional[lseq.Seq]:
422422
"""Return None if a Seq is empty, the Seq otherwise."""
423-
if s.first is None and s.rest == lseq.empty():
423+
if s.is_empty:
424424
return None
425425
return s
426426

@@ -440,7 +440,7 @@ def concat(*seqs) -> lseq.Seq:
440440
"""Concatenate the sequences given by seqs into a single Seq."""
441441
allseqs = lseq.sequence(itertools.chain(*filter(None, map(to_seq, seqs))))
442442
if allseqs is None:
443-
return lseq.empty()
443+
return lseq.EMPTY
444444
return allseqs
445445

446446

basilisp/lang/seq.py

Lines changed: 59 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import itertools
12
from abc import ABC, abstractmethod
23
from typing import Iterator, Optional, TypeVar, Iterable, Any, Callable
34

45
from basilisp.lang.meta import Meta
56
from basilisp.lang.util import lrepr
7+
from basilisp.util import Maybe
68

79
T = TypeVar('T')
810

@@ -15,20 +17,28 @@ def __repr__(self):
1517

1618
@property
1719
@abstractmethod
18-
def first(self) -> T:
20+
def is_empty(self) -> bool:
1921
raise NotImplementedError()
2022

2123
@property
2224
@abstractmethod
23-
def rest(self) -> Optional["Seq[T]"]:
25+
def first(self) -> Optional[T]:
26+
raise NotImplementedError()
27+
28+
@property
29+
@abstractmethod
30+
def rest(self) -> "Seq[T]":
2431
raise NotImplementedError()
2532

2633
@abstractmethod
2734
def cons(self, elem):
2835
raise NotImplementedError()
2936

3037
def __eq__(self, other):
31-
for e1, e2 in zip(self, other):
38+
sentinel = object()
39+
for e1, e2 in itertools.zip_longest(self, other, fillvalue=sentinel):
40+
if bool(e1 is sentinel) or bool(e2 is sentinel):
41+
return False
3242
if e1 != e2:
3343
return False
3444
return True
@@ -47,20 +57,50 @@ def seq(self) -> Seq[T]:
4757
raise NotImplementedError()
4858

4959

60+
class _EmptySequence(Seq[T]):
61+
def __repr__(self):
62+
return '()'
63+
64+
def __bool__(self):
65+
return False
66+
67+
@property
68+
def is_empty(self) -> bool:
69+
return True
70+
71+
@property
72+
def first(self) -> Optional[T]:
73+
return None
74+
75+
@property
76+
def rest(self) -> Seq[T]:
77+
return self
78+
79+
def cons(self, elem):
80+
return Cons(elem, self)
81+
82+
83+
EMPTY: Seq = _EmptySequence()
84+
85+
5086
class Cons(Seq, Meta):
5187
__slots__ = ('_first', '_rest', '_meta')
5288

5389
def __init__(self, first=None, seq: Optional[Seq[Any]] = None, meta=None) -> None:
5490
self._first = first
55-
self._rest = seq
91+
self._rest = Maybe(seq).or_else_get(EMPTY)
5692
self._meta = meta
5793

5894
@property
59-
def first(self):
95+
def is_empty(self) -> bool:
96+
return False
97+
98+
@property
99+
def first(self) -> Optional[Any]:
60100
return self._first
61101

62102
@property
63-
def rest(self) -> Optional[Seq[Any]]:
103+
def rest(self) -> Seq[Any]:
64104
return self._rest
65105

66106
def cons(self, elem) -> "Cons":
@@ -92,19 +132,23 @@ def __init__(self, s: Iterator, first: T) -> None:
92132
self._rest: Optional[Seq] = None # pylint:disable=assigning-non-slot
93133

94134
@property
95-
def first(self) -> T:
135+
def is_empty(self) -> bool:
136+
return False
137+
138+
@property
139+
def first(self) -> Optional[T]:
96140
return self._first
97141

98142
@property
99-
def rest(self) -> Optional["Seq[T]"]:
143+
def rest(self) -> "Seq[T]":
100144
if self._rest:
101145
return self._rest
102146

103147
try:
104148
n = next(self._seq)
105149
self._rest = _Sequence(self._seq, n) # pylint:disable=assigning-non-slot
106150
except StopIteration:
107-
self._rest = _EmptySequence() # pylint:disable=assigning-non-slot
151+
self._rest = EMPTY # pylint:disable=assigning-non-slot
108152

109153
return self._rest
110154

@@ -132,31 +176,27 @@ def _realize(self):
132176
def is_empty(self) -> bool:
133177
if not self._realized:
134178
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:
179+
if self._seq is None or self._seq.is_empty:
140180
return True
141181
return False
142182

143183
@property
144-
def first(self) -> T:
184+
def first(self) -> Optional[T]:
145185
if not self._realized:
146186
self._realize()
147187
try:
148188
return self._seq.first # type: ignore
149189
except AttributeError:
150-
return None # type: ignore
190+
return None
151191

152192
@property
153-
def rest(self) -> Optional["Seq[T]"]:
193+
def rest(self) -> "Seq[T]":
154194
if not self._realized:
155195
self._realize()
156196
try:
157197
return self._seq.rest # type: ignore
158198
except AttributeError:
159-
return empty()
199+
return EMPTY
160200

161201
def cons(self, elem):
162202
return Cons(elem, self)
@@ -176,33 +216,10 @@ def __iter__(self):
176216
o = o.rest
177217

178218

179-
class _EmptySequence(Seq[T]):
180-
def __repr__(self):
181-
return '()'
182-
183-
def __bool__(self):
184-
return False
185-
186-
@property
187-
def first(self):
188-
return None
189-
190-
@property
191-
def rest(self):
192-
return _EmptySequence()
193-
194-
def cons(self, elem):
195-
return Cons(elem, self)
196-
197-
198219
def sequence(s: Iterable) -> Seq[Any]:
199220
"""Create a Sequence from Iterable s."""
200221
try:
201222
i = iter(s)
202223
return _Sequence(i, next(i))
203224
except StopIteration:
204-
return _EmptySequence()
205-
206-
207-
def empty() -> Seq[Any]:
208-
return _EmptySequence()
225+
return EMPTY

tests/runtime_test.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ def test_first():
2222

2323
def test_rest():
2424
assert None is runtime.rest(None)
25-
assert llist.l() == runtime.rest(llist.l())
26-
assert llist.l() == runtime.rest(llist.l(1))
25+
assert lseq.EMPTY is runtime.rest(llist.l())
26+
assert lseq.EMPTY is runtime.rest(llist.l(1))
2727
assert llist.l(2, 3) == runtime.rest(llist.l(1, 2, 3))
28-
assert llist.l() == runtime.rest(vec.v(1).seq())
29-
assert llist.l() == runtime.rest(vec.v(1))
28+
assert lseq.EMPTY is runtime.rest(vec.v(1).seq())
29+
assert lseq.EMPTY is runtime.rest(vec.v(1))
3030
assert llist.l(2, 3) == runtime.rest(vec.v(1, 2, 3))
3131

3232

@@ -93,6 +93,8 @@ def test_to_seq():
9393
assert None is runtime.to_seq(lset.Set.empty())
9494
assert None is runtime.to_seq("")
9595

96+
assert None is not runtime.to_seq(llist.l(None))
97+
assert None is not runtime.to_seq(llist.l(None, None, None))
9698
assert None is not runtime.to_seq(llist.l(1))
9799
assert None is not runtime.to_seq(vec.v(1))
98100
assert None is not runtime.to_seq(lmap.map({"a": 1}))
@@ -117,10 +119,10 @@ def test_to_seq():
117119

118120
def test_concat():
119121
s1 = runtime.concat()
120-
assert llist.l() == s1
122+
assert lseq.EMPTY is s1
121123

122124
s1 = runtime.concat(llist.List.empty(), llist.List.empty())
123-
assert llist.l() == s1
125+
assert lseq.EMPTY == s1
124126

125127
s1 = runtime.concat(llist.List.empty(), llist.l(1, 2, 3))
126128
assert s1 == llist.l(1, 2, 3)
@@ -132,7 +134,9 @@ def test_concat():
132134
def test_apply():
133135
assert vec.v() == runtime.apply(vec.v, [[]])
134136
assert vec.v(1, 2, 3) == runtime.apply(vec.v, [[1, 2, 3]])
137+
assert vec.v(None, None, None) == runtime.apply(vec.v, [[None, None, None]])
135138
assert vec.v(vec.v(1, 2, 3), 4, 5, 6) == runtime.apply(vec.v, [vec.v(1, 2, 3), [4, 5, 6]])
139+
assert vec.v(vec.v(1, 2, 3), None, None, None) == runtime.apply(vec.v, [vec.v(1, 2, 3), [None, None, None]])
136140

137141

138142
def test_nth():

0 commit comments

Comments
 (0)