Skip to content

Commit bb76fa5

Browse files
authored
Support for Transient Collections (#568)
Add support for Transient collection types. Maps, sets, and vectors all support transient variants. Transient collections are mutable variants of the builtin persistent collections that can be used for building and modifying collections much faster than using the standard persistent data structures and modification ops.
1 parent 5c6972e commit bb76fa5

File tree

10 files changed

+669
-56
lines changed

10 files changed

+669
-56
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1818
* Added JSON encoder and decoder in `basilisp.json` namespace (#484)
1919
* Added support for generically diffing Basilisp data structures in `basilisp.data` namespace (#555)
2020
* Added support for artificially abstract bases classes in `deftype`, `defrecord`, and `reify` types (#565)
21+
* Added support for transient maps, sets, and vectors (#568)
2122

2223
### Changed
2324
* Basilisp set and map types are now backed by the HAMT provided by `immutables` (#557)

src/basilisp/core.lpy

Lines changed: 113 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -254,19 +254,25 @@
254254
(def
255255
^{:doc "Associate keys to values in associative data structure m. If m is nil,
256256
returns a new map with key-values kvs."
257-
:arglists '([m & kvs])}
257+
:arglists '([m k v] [m k v & kvs])}
258258
assoc
259-
(fn assoc [m & kvs]
260-
(apply basilisp.lang.runtime/assoc m kvs)))
259+
(fn assoc
260+
([m k v]
261+
(basilisp.lang.runtime/assoc m k v))
262+
([m k v & kvs]
263+
(apply basilisp.lang.runtime/assoc m k v kvs))))
261264

262265
(def
263266
^{:doc "Conjoin xs to collection. New elements may be added in different positions
264267
depending on the type of coll. conj returns the same type as coll. If coll
265268
is nil, return a list with xs conjoined."
266-
:arglists '([coll & xs])}
269+
:arglists '([coll x] [coll x & xs])}
267270
conj
268-
(fn conj [coll & xs]
269-
(apply basilisp.lang.runtime/conj coll xs)))
271+
(fn conj
272+
([coll x]
273+
(basilisp.lang.runtime/conj coll x))
274+
([coll x & xs]
275+
(apply basilisp.lang.runtime/conj coll x xs))))
270276

271277
(def
272278
^{:macro true
@@ -2014,6 +2020,86 @@
20142020
[m]
20152021
(seq (.values m)))
20162022

2023+
;;;;;;;;;;;;;;;;;;;;;;;;;;;
2024+
;; Transient Collections ;;
2025+
;;;;;;;;;;;;;;;;;;;;;;;;;;;
2026+
2027+
(defn transient
2028+
"Return a transient copy of persistent collection `coll`.
2029+
2030+
Transients can be created from maps, sets, and vectors. Transients allow faster
2031+
mutations than their persistent counterparts and are useful for performance
2032+
sensitive code which makes many modifications to these data structures.
2033+
2034+
Transient collections can be edited with the transient versions of the familiar
2035+
collection functions (depending on the type of `coll`): `assoc!`, `conj!`,
2036+
`disj!`, `dissoc!`, and `pop!`. Transient collections are designed to be used
2037+
in the same style as the builtin persistent collections, so callers should be
2038+
sure to use the returned value from prior calls to these collection functions
2039+
rather than repeatedly modifying the collection in place.
2040+
2041+
Once you have completed all of your modifications to the local transient
2042+
collection, you can call `persistent!` to return a persistent version of that
2043+
data structure."
2044+
[coll]
2045+
(if (instance? basilisp.lang.interfaces/IEvolveableCollection coll)
2046+
(.to-transient coll)
2047+
(throw
2048+
(ex-info (str "Object of type " (type coll) " does not implement "
2049+
"IEvolveableCollection interface")
2050+
{:coll coll
2051+
:type (type coll)}))))
2052+
2053+
(defn persistent!
2054+
"Return a persistent copy of the transient collection `coll` which was created
2055+
by calling `transient`."
2056+
[coll]
2057+
(if (instance? basilisp.lang.interfaces/ITransientCollection coll)
2058+
(.to-persistent coll)
2059+
(throw
2060+
(ex-info (str "Object of type " (type coll) " does not implement "
2061+
"ITransientCollection interface")
2062+
{:coll coll
2063+
:type (type coll)}))))
2064+
2065+
(defn assoc!
2066+
"Associate keys to values in the transient associative data structure `m`."
2067+
([m k v]
2068+
(.assoc-transient m k v))
2069+
([m k v & kvs]
2070+
(apply (.-assoc-transient m) k v kvs)))
2071+
2072+
(defn conj!
2073+
"Conjoin xs to the transient collection `coll`. New elements may be added in
2074+
different positions depending on the type of `coll`. conj returns the same type
2075+
as `coll`."
2076+
([] (transient []))
2077+
([coll] coll)
2078+
([coll & xs]
2079+
(apply (.-cons-transient coll) xs)))
2080+
2081+
(defn disj!
2082+
"Return a new version of the transient set `s` without the given elements. If the
2083+
elements don't exist in `s`, they are ignored."
2084+
([s] s)
2085+
([s elem]
2086+
(.disj-transient s elem))
2087+
([s elem & elems]
2088+
(apply (.-disj-transient s) elem elems)))
2089+
2090+
(defn dissoc!
2091+
"Return a new version of the transient associative collection `m`` without the
2092+
given keys. If the keys don't exist in `m`, they are ignored."
2093+
([m k] (.dissoc-transient m k))
2094+
([m k & ks]
2095+
(apply (.-dissoc-transient m) k ks)))
2096+
2097+
(defn pop!
2098+
"Return a new transient vector without the last element of `coll`. If `coll` is
2099+
empty, throw an exception."
2100+
[coll]
2101+
(.pop-transient coll))
2102+
20172103
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
20182104
;; Higher Order and Collection Functions ;;
20192105
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
@@ -2634,9 +2720,10 @@
26342720
appears in a map, the rightmost map's value for that key will be taken."
26352721
[& maps]
26362722
(when (some identity maps)
2637-
(reduce #(conj %1 %2)
2638-
{}
2639-
maps)))
2723+
(persistent!
2724+
(reduce #(conj! %1 %2)
2725+
(transient {})
2726+
maps))))
26402727

26412728
(defn trampoline
26422729
"Trampoline f with starting arguments. If f returns an fn, call its return
@@ -4080,12 +4167,13 @@
40804167
associative collection smap, if they exist."
40814168
[smap coll]
40824169
(if (vector? coll)
4083-
(reduce (fn [res v]
4084-
(if-let [newv (get smap v)]
4085-
(conj res newv)
4086-
res))
4087-
[]
4088-
coll)
4170+
(persistent!
4171+
(reduce (fn [res v]
4172+
(if-let [newv (get smap v)]
4173+
(conj! res newv)
4174+
res))
4175+
(transient [])
4176+
coll))
40894177
(map (fn [v]
40904178
(if-let [newv (get smap v)]
40914179
newv
@@ -4095,12 +4183,13 @@
40954183
(defn select-keys
40964184
"Return a map with only the keys of m which are in ks."
40974185
[m ks]
4098-
(reduce (fn [new-map k]
4099-
(if (contains? m k)
4100-
(assoc new-map k (get m k))
4101-
new-map))
4102-
{}
4103-
ks))
4186+
(persistent!
4187+
(reduce (fn [new-map k]
4188+
(if (contains? m k)
4189+
(assoc! new-map k (get m k))
4190+
new-map))
4191+
(transient {})
4192+
ks)))
41044193

41054194
;;;;;;;;;;;;;;;;;;;;;;;;;;;
41064195
;; Destructuring Support ;;
@@ -4845,8 +4934,9 @@
48454934
(reduce (fn [m [method-name arities]]
48464935
(->> (map rest arities)
48474936
(apply list `fn)
4848-
(assoc m (keyword (name method-name)))))
4849-
{})))
4937+
(assoc! m (keyword (name method-name)))))
4938+
(transient {}))
4939+
(persistent!)))
48504940

48514941
(defmacro extend-protocol
48524942
"Extend a Protocol with implementations for multiple types.

src/basilisp/lang/interfaces.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,95 @@ def seq(self) -> "ISeq[T]": # type: ignore[override]
272272
raise NotImplementedError()
273273

274274

275+
T_tcoll = TypeVar("T_tcoll", bound="ITransientCollection", covariant=True)
276+
277+
# Including ABC as a base seems to cause catastrophic meltdown.
278+
class IEvolveableCollection(Generic[T_tcoll]):
279+
@abstractmethod
280+
def to_transient(self) -> T_tcoll:
281+
raise NotImplementedError()
282+
283+
284+
class ITransientCollection(Generic[T]):
285+
__slots__ = ()
286+
287+
@abstractmethod
288+
def cons_transient(self: T_tcoll, *elems: T) -> "T_tcoll":
289+
raise NotImplementedError()
290+
291+
@abstractmethod
292+
def to_persistent(self: T_tcoll) -> "IPersistentCollection[T]":
293+
raise NotImplementedError()
294+
295+
296+
T_tassoc = TypeVar("T_tassoc", bound="ITransientAssociative")
297+
298+
299+
class ITransientAssociative(ILookup[K, V], ITransientCollection[IMapEntry[K, V]]):
300+
__slots__ = ()
301+
302+
@abstractmethod
303+
def assoc_transient(self: T_tassoc, *kvs) -> T_tassoc:
304+
raise NotImplementedError()
305+
306+
@abstractmethod
307+
def contains_transient(self, k: K) -> bool:
308+
raise NotImplementedError()
309+
310+
@abstractmethod
311+
def entry_transient(self, k: K) -> Optional[IMapEntry[K, V]]:
312+
raise NotImplementedError()
313+
314+
315+
T_tmap = TypeVar("T_tmap", bound="ITransientMap")
316+
317+
318+
class ITransientMap(ICounted, ITransientAssociative[K, V]):
319+
__slots__ = ()
320+
321+
@abstractmethod
322+
def cons_transient( # type: ignore[override]
323+
self: T_tmap, *elems: Union[IMapEntry[K, V], "IPersistentMap[K, V]", None]
324+
) -> T_tmap:
325+
raise NotImplementedError()
326+
327+
@abstractmethod
328+
def dissoc_transient(self: T_tmap, *ks: K) -> T_tmap:
329+
raise NotImplementedError()
330+
331+
332+
T_tset = TypeVar("T_tset", bound="ITransientSet")
333+
334+
335+
class ITransientSet(ICounted, ITransientCollection[T]):
336+
__slots__ = ()
337+
338+
@abstractmethod
339+
def disj_transient(self: T_tset, *elems: T) -> T_tset:
340+
raise NotImplementedError()
341+
342+
343+
T_tvec = TypeVar("T_tvec", bound="ITransientVector")
344+
345+
346+
class ITransientVector(
347+
ITransientAssociative[int, T], IIndexed,
348+
):
349+
__slots__ = ()
350+
351+
@abstractmethod
352+
def assoc_transient(self: T_tvec, *kvs) -> T_tvec: # type: ignore[override]
353+
raise NotImplementedError()
354+
355+
@abstractmethod
356+
def cons_transient(self: T_tvec, *elems: T) -> T_tvec: # type: ignore[override]
357+
raise NotImplementedError()
358+
359+
@abstractmethod
360+
def pop_transient(self: T_tvec) -> T_tvec:
361+
raise NotImplementedError()
362+
363+
275364
class IRecord(ILispObject):
276365
"""IRecord is a marker interface for types def'ed by `defrecord` forms.
277366

0 commit comments

Comments
 (0)