Skip to content

Commit c242215

Browse files
authored
Add Collection ABC and conj (#109)
* Add Collection ABC and conj * Match list empty method to Collection ABC * Silence linter
1 parent 28be02b commit c242215

File tree

11 files changed

+154
-32
lines changed

11 files changed

+154
-32
lines changed

basilisp/lang/collection.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
from abc import ABC, abstractmethod
2+
3+
4+
class Collection(ABC):
5+
__slots__ = ()
6+
7+
@abstractmethod
8+
def cons(self, *elems):
9+
raise NotImplementedError()
10+
11+
@staticmethod
12+
@abstractmethod
13+
def empty() -> "Collection":
14+
raise NotImplementedError()

basilisp/lang/list.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from pyrsistent import plist, PList
22

3+
from basilisp.lang.collection import Collection
34
from basilisp.lang.meta import Meta
45
from basilisp.lang.seq import Seq
56
from basilisp.lang.util import lrepr
67

78

8-
class List(Meta, Seq):
9+
class List(Collection, Meta, Seq):
910
"""Basilisp List. Delegates internally to a pyrsistent.PList object.
1011
1112
Do not instantiate directly. Instead use the l() and list() factory
@@ -53,14 +54,14 @@ def first(self):
5354
def rest(self) -> "List":
5455
return List(self._inner.rest)
5556

56-
def conj(self, elem) -> "List":
57-
return List(self._inner.cons(elem))
58-
59-
def cons(self, elem) -> "List":
60-
return List(self._inner.cons(elem))
57+
def cons(self, *elems) -> "List":
58+
l = self._inner
59+
for elem in elems:
60+
l = l.cons(elem)
61+
return List(l)
6162

6263
@staticmethod
63-
def empty(meta=None) -> "List":
64+
def empty(meta=None) -> "List": # pylint:disable=arguments-differ
6465
return l(meta=meta)
6566

6667

basilisp/lang/map.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from collections import Sequence
2-
from typing import Any, Optional # noqa: F401
2+
from typing import Optional # noqa: F401
33

44
from functional import seq
55
from pyrsistent import pmap, PMap
66

77
import basilisp.lang.vector as vec
88
from basilisp.lang.associative import Associative
9+
from basilisp.lang.collection import Collection
910
from basilisp.lang.meta import Meta
1011
from basilisp.lang.seq import Seqable, sequence, Seq
1112
from basilisp.lang.util import lrepr
@@ -58,7 +59,7 @@ def from_vec(v: Sequence) -> "MapEntry":
5859
return MapEntry(vec.vector(v))
5960

6061

61-
class Map(Associative, Meta, Seqable):
62+
class Map(Associative, Collection, Meta, Seqable):
6263
"""Basilisp Map. Delegates internally to a pyrsistent.PMap object.
6364
Do not instantiate directly. Instead use the m() and map() factory
6465
methods below."""
@@ -148,18 +149,24 @@ def update(self, *maps) -> "Map":
148149
m: PMap = self._inner.update(*maps)
149150
return Map(m)
150151

151-
def _conj(self, entry: MapEntry) -> "Map":
152+
def _cons(self, *entries) -> "Map":
152153
try:
153-
return Map(self._inner.set(entry.key, entry.value), meta=self.meta)
154+
e = self._inner.evolver()
155+
for entry in entries:
156+
e.set(entry.key, entry.value)
157+
return Map(e.persistent(), meta=self.meta)
154158
except AttributeError:
155159
raise ValueError(
156160
"Argument to map conj must be castable to MapEntry")
157161

158-
def conj(self, entry: Any) -> "Map":
162+
def cons(self, *entries) -> "Map":
159163
try:
160-
return Map(self._inner.set(entry.key, entry.value), meta=self.meta)
164+
e = self._inner.evolver()
165+
for entry in entries:
166+
e.set(entry.key, entry.value)
167+
return Map(e.persistent(), meta=self.meta)
161168
except AttributeError:
162-
return self._conj(MapEntry.from_vec(entry))
169+
return self._cons(*seq(entries).map(MapEntry.from_vec))
163170

164171
@staticmethod
165172
def empty() -> "Map":

basilisp/lang/runtime.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from pyrsistent import pmap, PMap, PSet, pset
99

1010
import basilisp.lang.associative as lassoc
11+
import basilisp.lang.collection as lcoll
1112
import basilisp.lang.list as llist
1213
import basilisp.lang.map as lmap
1314
import basilisp.lang.seq as lseq
@@ -487,6 +488,16 @@ def assoc(m, *kvs):
487488
raise TypeError(f"Object of type {type(m)} does not implement Associative interface")
488489

489490

491+
def conj(coll, *xs):
492+
"""Conjoin xs to collection. """
493+
if coll is None:
494+
l = llist.List.empty()
495+
return l.cons(*xs)
496+
if isinstance(coll, lcoll.Collection):
497+
return coll.cons(*xs)
498+
raise TypeError(f"Object of type {type(coll)} does not implement Collection interface")
499+
500+
490501
def _collect_args(args) -> lseq.Seq:
491502
"""Collect Python starred arguments into a Basilisp list."""
492503
if isinstance(args, tuple):

basilisp/lang/set.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

33
from pyrsistent import PSet, pset
44

5+
from basilisp.lang.collection import Collection
56
from basilisp.lang.map import Map
67
from basilisp.lang.meta import Meta
78
from basilisp.lang.seq import Seqable, Seq, sequence
89
from basilisp.lang.util import lrepr
910

1011

11-
class Set(Meta, Seqable):
12+
class Set(Collection, Meta, Seqable):
1213
"""Basilisp Set. Delegates internally to a pyrsistent.PSet object.
1314
1415
Do not instantiate directly. Instead use the s() and set() factory
@@ -51,8 +52,11 @@ def with_meta(self, meta: Map) -> "Set":
5152
meta)
5253
return set(self._inner, meta=new_meta)
5354

54-
def conj(self, elem) -> "Set":
55-
return Set(self._inner.add(elem), meta=self.meta)
55+
def cons(self, *elems) -> "Set":
56+
e = self._inner.evolver()
57+
for elem in elems:
58+
e.add(elem)
59+
return Set(e.persistent(), meta=self.meta)
5660

5761
@staticmethod
5862
def empty() -> "Set":

basilisp/lang/vector.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
from pyrsistent import PVector, pvector
22

33
from basilisp.lang.associative import Associative
4+
from basilisp.lang.collection import Collection
45
from basilisp.lang.meta import Meta
56
from basilisp.lang.seq import Seqable, Seq, sequence
67
from basilisp.lang.util import lrepr
78

89

9-
class Vector(Associative, Meta, Seqable):
10+
class Vector(Associative, Collection, Meta, Seqable):
1011
"""Basilisp Vector. Delegates internally to a pyrsistent.PVector object.
1112
Do not instantiate directly. Instead use the v() and vec() factory
1213
methods below."""
@@ -45,8 +46,11 @@ def with_meta(self, meta) -> "Vector":
4546
meta)
4647
return vector(self._inner, meta=new_meta)
4748

48-
def conj(self, elem) -> "Vector":
49-
return Vector(self._inner.append(elem), meta=self.meta)
49+
def cons(self, *elems) -> "Vector":
50+
e = self._inner.evolver()
51+
for elem in elems:
52+
e.append(elem)
53+
return Vector(e.persistent(), meta=self.meta)
5054

5155
def assoc(self, *kvs):
5256
return Vector(self._inner.mset(*kvs))

tests/list_test.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import basilisp.lang.collection as lcoll
12
import basilisp.lang.list as llist
23
import basilisp.lang.map as lmap
34
import basilisp.lang.meta as meta
@@ -6,6 +7,11 @@
67
from basilisp.lang.symbol import symbol
78

89

10+
def test_list_collection_interface():
11+
assert isinstance(llist.l(), lcoll.Collection)
12+
assert issubclass(llist.List, lcoll.Collection)
13+
14+
915
def test_list_meta_interface():
1016
assert isinstance(llist.l(), meta.Meta)
1117
assert issubclass(llist.List, meta.Meta)
@@ -20,10 +26,10 @@ def test_list_slice():
2026
assert isinstance(llist.l(1, 2, 3)[1:], llist.List)
2127

2228

23-
def test_list_conj():
29+
def test_list_cons():
2430
meta = lmap.m(tag="async")
2531
l1 = llist.l(keyword("kw1"), meta=meta)
26-
l2 = l1.conj(keyword("kw2"))
32+
l2 = l1.cons(keyword("kw2"))
2733
assert l1 is not l2
2834
assert l1 != l2
2935
assert len(l2) == 2

tests/map_test.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22

33
import basilisp.lang.associative as lassoc
4+
import basilisp.lang.collection as lcoll
45
import basilisp.lang.map as lmap
56
import basilisp.lang.meta as meta
67
import basilisp.lang.seq as lseq
@@ -14,6 +15,11 @@ def test_map_associative_interface():
1415
assert issubclass(lmap.Map, lassoc.Associative)
1516

1617

18+
def test_map_collection_interface():
19+
assert isinstance(lmap.m(), lcoll.Collection)
20+
assert issubclass(lmap.Map, lcoll.Collection)
21+
22+
1723
def test_map_meta_interface():
1824
assert isinstance(lmap.m(), meta.Meta)
1925
assert issubclass(lmap.Map, meta.Meta)
@@ -61,28 +67,51 @@ def test_entry():
6167
assert None is lmap.Map.empty().entry("a")
6268

6369

64-
def test_map_conj():
70+
def test_map_cons():
6571
meta = lmap.m(tag="async")
6672
m1 = lmap.map({"first": "Chris"}, meta=meta)
67-
m2 = m1.conj(MapEntry.of("last", "Cronk"))
73+
m2 = m1.cons(MapEntry.of("last", "Cronk"))
6874
assert m1 is not m2
6975
assert m1 != m2
7076
assert len(m2) == 2
7177
assert meta == m1.meta
7278
assert meta == m2.meta
79+
assert "Chris" == m1.get("first")
80+
assert not m1.contains("last")
81+
assert "Cronk" == m2.get("last")
82+
assert "Chris" == m2.get("first")
7383

7484
meta = lmap.m(tag="async")
7585
m1 = lmap.map({"first": "Chris"}, meta=meta)
76-
m2 = m1.conj(["last", "Cronk"])
86+
m2 = m1.cons(["last", "Cronk"])
7787
assert m1 is not m2
7888
assert m1 != m2
7989
assert len(m2) == 2
8090
assert meta == m1.meta
8191
assert meta == m2.meta
92+
assert "Chris" == m1.get("first")
93+
assert not m1.contains("last")
94+
assert "Cronk" == m2.get("last")
95+
assert "Chris" == m2.get("first")
96+
97+
meta = lmap.m(tag="async")
98+
m1 = lmap.map({"first": "Chris"}, meta=meta)
99+
m2 = m1.cons(["last", "Cronk"], ["middle", "L"])
100+
assert m1 is not m2
101+
assert m1 != m2
102+
assert len(m2) == 3
103+
assert meta == m1.meta
104+
assert meta == m2.meta
105+
assert "Chris" == m1.get("first")
106+
assert not m1.contains("middle")
107+
assert not m1.contains("last")
108+
assert "Cronk" == m2.get("last")
109+
assert "L" == m2.get("middle")
110+
assert "Chris" == m2.get("first")
82111

83112
with pytest.raises(ValueError):
84113
m1 = lmap.map({"first": "Chris"})
85-
m1.conj(["last"])
114+
m1.cons(["last"])
86115

87116

88117
def test_map_meta():

tests/runtime_test.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,37 @@ def test_assoc():
167167

168168
with pytest.raises(TypeError):
169169
runtime.assoc(llist.List.empty(), 1, "a")
170+
171+
172+
def test_conj():
173+
assert llist.l(1) == runtime.conj(None, 1)
174+
assert llist.l(3, 2, 1) == runtime.conj(None, 1, 2, 3)
175+
assert llist.l(llist.l(1, 2, 3)) == runtime.conj(None, llist.l(1, 2, 3))
176+
177+
assert llist.l(1) == runtime.conj(llist.List.empty(), 1)
178+
assert llist.l(3, 2, 1) == runtime.conj(llist.List.empty(), 1, 2, 3)
179+
assert llist.l(3, 2, 1, 1) == runtime.conj(llist.l(1), 1, 2, 3)
180+
assert llist.l(llist.l(1, 2, 3), 1) == runtime.conj(llist.l(1), llist.l(1, 2, 3))
181+
182+
assert lset.s(1) == runtime.conj(lset.Set.empty(), 1)
183+
assert lset.s(1, 2, 3) == runtime.conj(lset.Set.empty(), 1, 2, 3)
184+
assert lset.s(1, 2, 3) == runtime.conj(lset.s(1), 1, 2, 3)
185+
assert lset.s(1, lset.s(1, 2, 3)) == runtime.conj(lset.s(1), lset.s(1, 2, 3))
186+
187+
assert vec.v(1) == runtime.conj(vec.Vector.empty(), 1)
188+
assert vec.v(1, 2, 3) == runtime.conj(vec.Vector.empty(), 1, 2, 3)
189+
assert vec.v(1, 1, 2, 3) == runtime.conj(vec.v(1), 1, 2, 3)
190+
assert vec.v(1, vec.v(1, 2, 3)) == runtime.conj(vec.v(1), vec.v(1, 2, 3))
191+
192+
assert lmap.map({"a": 1}) == runtime.conj(lmap.Map.empty(), ["a", 1])
193+
assert lmap.map({"a": 1, "b": 93}) == runtime.conj(lmap.Map.empty(), ["a", 1], ["b", 93])
194+
assert lmap.map({"a": 1, "b": 93}) == runtime.conj(lmap.map({"a": 8}), ["a", 1], ["b", 93])
195+
196+
with pytest.raises(ValueError):
197+
runtime.conj(lmap.map({"a": 8}), "a", 1, "b", 93)
198+
199+
with pytest.raises(TypeError):
200+
runtime.conj(3, 1, "a")
201+
202+
with pytest.raises(TypeError):
203+
runtime.conj("b", 1, "a")

tests/set_test.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import basilisp.lang.collection as lcoll
12
import basilisp.lang.map as lmap
23
import basilisp.lang.meta as meta
34
import basilisp.lang.seq as lseq
@@ -6,6 +7,11 @@
67
from basilisp.lang.symbol import symbol
78

89

10+
def test_list_collection_interface():
11+
assert isinstance(lset.s(), lcoll.Collection)
12+
assert issubclass(lset.Set, lcoll.Collection)
13+
14+
915
def test_list_meta_interface():
1016
assert isinstance(lset.s(), meta.Meta)
1117
assert issubclass(lset.Set, meta.Meta)
@@ -19,7 +25,7 @@ def test_set_seqable_interface():
1925
def test_set_conj():
2026
meta = lmap.m(tag="async")
2127
s1 = lset.s(keyword("kw1"), meta=meta)
22-
s2 = s1.conj(keyword("kw2"))
28+
s2 = s1.cons(keyword("kw2"))
2329
assert s1 is not s2
2430
assert s1 != s2
2531
assert len(s2) == 2

0 commit comments

Comments
 (0)