Skip to content

Commit b88f512

Browse files
authored
Add atoms to core lib, supplement delay (#133)
* Add atoms to core lib, supplement delay * Add runtime tests * Fix linter errors
1 parent 17c1c35 commit b88f512

File tree

10 files changed

+159
-20
lines changed

10 files changed

+159
-20
lines changed

basilisp/core/__init__.lpy

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,33 @@
216216
(if (seq (rest args))
217217
(recur (operator/add x (first args)) (rest args))
218218
(operator/add x (first args)))))
219+
220+
(defn deref
221+
"Dereference an atom and returns its contents."
222+
[o]
223+
(basilisp.lang.runtime/deref o))
224+
225+
(defn reset!
226+
"Reset the value of an atom to v without regard to the previous value.
227+
Return the new value."
228+
[atom v]
229+
(let [current (deref atom)]
230+
(if (.compare-and-set atom current v)
231+
v
232+
(recur atom v))))
233+
234+
(defn swap!
235+
"Atomically swap the value of an atom to the return value of (apply f
236+
current-value args). The function f may be called multiple times while
237+
swapping, so should be free of side effects. Return the new value."
238+
[atom f & args]
239+
(apply basilisp.lang.runtime/swap atom f args))
240+
241+
(defn atom
242+
"Return an Atom containing v. The value of an Atom at any point in time
243+
may be returned by deref'ing it. The value of an atom may be reset using
244+
reset! and may be swapped using swap!. All operations on an atom occur
245+
atomically."
246+
[v]
247+
(basilisp.lang.atom/Atom v))
248+

basilisp/lang/atom.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,26 @@
1-
import atomos.atom as atom
1+
from typing import Generic, TypeVar, Callable
22

3-
Atom = atom.Atom
3+
import atomos.atom
4+
5+
from basilisp.lang.deref import Deref
6+
7+
T = TypeVar('T')
8+
9+
10+
class Atom(Deref, Generic[T]):
11+
__slots__ = ('_atom',)
12+
13+
def __init__(self, state: T) -> None:
14+
self._atom = atomos.atom.Atom(state)
15+
16+
def compare_and_set(self, old, new):
17+
return self._atom.compare_and_set(old, new)
18+
19+
def deref(self) -> T:
20+
return self._atom.deref()
21+
22+
def reset(self, v: T) -> T:
23+
return self._atom.reset(v)
24+
25+
def swap(self, f: Callable[..., T], *args, **kwargs) -> T:
26+
return self._atom.swap(f, *args, **kwargs)

basilisp/lang/delay.py

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,28 @@
1-
from typing import Callable, Any
1+
from typing import Callable, TypeVar
22

33
import basilisp.lang.atom as atom
44
import basilisp.lang.map as lmap
5+
from basilisp.lang.deref import Deref
56

7+
T = TypeVar('T')
68

7-
class Delay:
9+
10+
class Delay(Deref[T]):
811
__slots__ = ('_state',)
912

10-
def __init__(self, f: Callable[[], Any]) -> None:
11-
self._state = atom.Atom(lmap.m(f=f, value=None, computed=False))
13+
def __init__(self, f: Callable[[], T]) -> None:
14+
self._state = atom.Atom(lmap.m(f=f, value=None, computed=False)) # pylint:disable=assigning-non-slot
15+
16+
@staticmethod
17+
def __deref(m: lmap.Map):
18+
if m["computed"]:
19+
return m
20+
else:
21+
return m.assoc("value", m["f"](), "computed", True)
1222

13-
def deref(self) -> Any:
14-
def __deref(m: lmap.Map):
15-
if m["computed"]:
16-
return m
17-
else:
18-
return m.assoc("value", m["f"](), "computed", True)
23+
def deref(self) -> T:
24+
return self._state.swap(Delay.__deref).value
1925

20-
return self._state.swap(__deref).value
26+
@property
27+
def is_realized(self) -> bool:
28+
return self._state.deref()["computed"]

basilisp/lang/deref.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from abc import ABC, abstractmethod
2+
from typing import TypeVar, Generic
3+
4+
T = TypeVar('T')
5+
6+
7+
class Deref(ABC, Generic[T]):
8+
__slots__ = ()
9+
10+
@abstractmethod
11+
def deref(self) -> T:
12+
raise NotImplementedError()

basilisp/lang/runtime.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import basilisp.lang.associative as lassoc
1111
import basilisp.lang.collection as lcoll
12+
import basilisp.lang.deref as lderef
1213
import basilisp.lang.list as llist
1314
import basilisp.lang.map as lmap
1415
import basilisp.lang.seq as lseq
@@ -176,6 +177,8 @@ class Namespace:
176177
namespace"""
177178
DEFAULT_IMPORTS = atom.Atom(pset(seq(['builtins',
178179
'operator',
180+
'basilisp.lang.atom',
181+
'basilisp.lang.delay',
179182
'basilisp.lang.exception',
180183
'basilisp.lang.keyword',
181184
'basilisp.lang.list',
@@ -498,6 +501,20 @@ def conj(coll, *xs):
498501
raise TypeError(f"Object of type {type(coll)} does not implement Collection interface")
499502

500503

504+
def deref(o):
505+
"""Dereference a Deref object and return its contents."""
506+
if isinstance(o, lderef.Deref):
507+
return o.deref()
508+
raise TypeError(f"Object of type {type(o)} cannot be dereferenced")
509+
510+
511+
def swap(a: atom.Atom, f, *args):
512+
"""Atomically swap the value of an atom to the return value of (apply f
513+
current-value args). The function f may be called multiple times while
514+
swapping, so should be free of side effects. Return the new value."""
515+
return a.swap(f, *args)
516+
517+
501518
def _collect_args(args) -> lseq.Seq:
502519
"""Collect Python starred arguments into a Basilisp list."""
503520
if isinstance(args, tuple):

basilisp/lang/seq.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ def cons(self, elem):
162162
return Cons(elem, self)
163163

164164
@property
165-
def realized(self):
165+
def is_realized(self):
166166
return self._realized
167167

168168
def __iter__(self):

tests/atom_test.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import basilisp.lang.atom as atom
2+
import basilisp.lang.deref as lderef
3+
import basilisp.lang.map as lmap
4+
import basilisp.lang.vector as vec
5+
6+
7+
def test_atom_deref_interface():
8+
assert isinstance(atom.Atom(1), lderef.Deref)
9+
assert issubclass(atom.Atom, lderef.Deref)
10+
11+
12+
def test_atom():
13+
a = atom.Atom(vec.Vector.empty())
14+
assert vec.Vector.empty() == a.deref()
15+
16+
assert vec.v(1) == a.swap(lambda v, e: v.cons(e), 1)
17+
assert vec.v(1) == a.deref()
18+
19+
assert vec.v(1, 2) == a.swap(lambda v, e: v.cons(e), 2)
20+
assert vec.v(1, 2) == a.deref()
21+
22+
assert vec.v(1, 2) == a.reset(lmap.Map.empty())
23+
assert lmap.Map.empty() == a.deref()

tests/delay_test.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,16 @@
55
def test_delay(capsys):
66
d = delay.Delay(lambda: vec.v(print("In Delay Now"), 1, 2, 3))
77

8+
assert False is d.is_realized
9+
810
assert vec.v(None, 1, 2, 3) == d.deref()
911
captured = capsys.readouterr()
1012
assert "In Delay Now\n" == captured.out
1113

14+
assert True is d.is_realized
15+
1216
assert vec.v(None, 1, 2, 3) == d.deref()
1317
captured = capsys.readouterr()
1418
assert "" == captured.out
19+
20+
assert True is d.is_realized

tests/runtime_test.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import pytest
22

3+
import basilisp.lang.atom as atom
34
import basilisp.lang.keyword as keyword
45
import basilisp.lang.list as llist
56
import basilisp.lang.map as lmap
@@ -203,6 +204,25 @@ def test_conj():
203204
runtime.conj("b", 1, "a")
204205

205206

207+
def test_deref():
208+
assert 1 == runtime.deref(atom.Atom(1))
209+
assert vec.Vector.empty() == runtime.deref(atom.Atom(vec.Vector.empty()))
210+
211+
with pytest.raises(TypeError):
212+
runtime.deref(1)
213+
214+
with pytest.raises(TypeError):
215+
runtime.deref(vec.Vector.empty())
216+
217+
218+
def test_swap():
219+
assert 3 == runtime.swap(atom.Atom(1), lambda x, y: x + y, 2)
220+
assert vec.v(1) == runtime.swap(atom.Atom(vec.Vector.empty()), lambda v, e: v.cons(e), 1)
221+
222+
with pytest.raises(AttributeError):
223+
runtime.swap(1, lambda x, y: x + y, 2)
224+
225+
206226
def test_trampoline_args():
207227
args = runtime._TrampolineArgs(True)
208228
assert () == args.args

tests/seq_test.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,21 @@ def test_lazy_sequence():
1313
assert not s.is_empty, "LazySeq has not been realized yet"
1414
assert None is s.first
1515
assert lseq.empty() == s.rest
16-
assert s.realized
16+
assert s.is_realized
1717
assert s.is_empty, "LazySeq has been realized and is empty"
1818

1919
s = lseq.LazySeq(lambda: lseq.empty())
2020
assert not s.is_empty, "LazySeq has not been realized yet"
2121
assert None is s.first
2222
assert lseq.empty() == s.rest
23-
assert s.realized
23+
assert s.is_realized
2424
assert s.is_empty, "LazySeq has been realized and is empty"
2525

2626
s = lseq.LazySeq(lambda: lseq.sequence([1]))
2727
assert not s.is_empty, "LazySeq has not been realized yet"
2828
assert 1 == s.first
2929
assert lseq.empty() == s.rest
30-
assert s.realized
30+
assert s.is_realized
3131
assert not s.is_empty, "LazySeq has been realized and is not empty"
3232

3333
def lazy_seq():
@@ -43,21 +43,21 @@ def inner_inner_seq():
4343
assert not s.is_empty, "LazySeq has not been realized yet"
4444
assert 1 == s.first
4545
assert isinstance(s.rest, lseq.LazySeq)
46-
assert s.realized
46+
assert s.is_realized
4747
assert not s.is_empty, "LazySeq has been realized and is not empty"
4848

4949
r = s.rest
5050
assert not r.is_empty, "LazySeq has not been realized yet"
5151
assert 2 == r.first
5252
assert isinstance(r.rest, lseq.LazySeq)
53-
assert r.realized
53+
assert r.is_realized
5454
assert not r.is_empty, "LazySeq has been realized and is not empty"
5555

5656
t = r.rest
5757
assert not t.is_empty, "LazySeq has not been realized yet"
5858
assert 3 == t.first
5959
assert lseq.empty() == t.rest
60-
assert t.realized
60+
assert t.is_realized
6161
assert not t.is_empty, "LazySeq has been realized and is not empty"
6262

6363
assert [1, 2, 3] == [e for e in s]

0 commit comments

Comments
 (0)