Skip to content

Commit edd7e17

Browse files
authored
Promises (#440)
* Promises * Force too I guess * Use the correct slots
1 parent e7b40eb commit edd7e17

File tree

6 files changed

+92
-1
lines changed

6 files changed

+92
-1
lines changed

src/basilisp/core.lpy

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,29 @@
821821
[x]
822822
(instance? basilisp.lang.delay/Delay x))
823823

824+
(defn force
825+
"If x is a Delay, returned the possibly cached value of x. Otherwise, return
826+
x."
827+
[x]
828+
(if (delay? x)
829+
@x
830+
x))
831+
832+
(defn promise
833+
"Return a promise object which can be set exactly once using `deliver`.
834+
Readers may block waiting for the value of the promise using `@` or `deref`.
835+
If the value has already been realized, then reading the value of the
836+
promise will not block. Readers may check if the promise has been delivered
837+
using `realized?`."
838+
[]
839+
(basilisp.lang.promise/Promise))
840+
841+
(defn deliver
842+
"Deliver the value v to the promise p. If p already has a value, then a
843+
subsequent call to `deliver` for p will have no effect."
844+
[p v]
845+
(.deliver p v))
846+
824847
;;;;;;;;;;;;;;;;;;;;;;;;;;
825848
;; Arithmetic Functions ;;
826849
;;;;;;;;;;;;;;;;;;;;;;;;;;

src/basilisp/lang/compiler/generator.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,7 @@ def _is_redefable(v: Var) -> bool:
434434
_LIST_ALIAS = genname("llist")
435435
_MAP_ALIAS = genname("lmap")
436436
_MULTIFN_ALIAS = genname("multifn")
437+
_PROMISE_ALIAS = genname("promise")
437438
_READER_ALIAS = genname("reader")
438439
_RUNTIME_ALIAS = genname("runtime")
439440
_SEQ_ALIAS = genname("seq")
@@ -454,6 +455,7 @@ def _is_redefable(v: Var) -> bool:
454455
"basilisp.lang.list": _LIST_ALIAS,
455456
"basilisp.lang.map": _MAP_ALIAS,
456457
"basilisp.lang.multifn": _MULTIFN_ALIAS,
458+
"basilisp.lang.promise": _PROMISE_ALIAS,
457459
"basilisp.lang.reader": _READER_ALIAS,
458460
"basilisp.lang.runtime": _RUNTIME_ALIAS,
459461
"basilisp.lang.seq": _SEQ_ALIAS,

src/basilisp/lang/delay.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def __deref(state: _DelayState) -> _DelayState:
3737
return _DelayState(f=state.f, value=state.f(), computed=True)
3838

3939
def deref(self) -> Optional[T]:
40-
return self._state.swap(Delay.__deref).value
40+
return self._state.swap(self.__deref).value
4141

4242
@property
4343
def is_realized(self) -> bool:

src/basilisp/lang/promise.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import threading
2+
from typing import Optional, TypeVar
3+
4+
from basilisp.lang.interfaces import IBlockingDeref
5+
6+
T = TypeVar("T")
7+
8+
9+
# pylint: disable=assigning-non-slot
10+
class Promise(IBlockingDeref[T]):
11+
__slots__ = ("_condition", "_is_delivered", "_value")
12+
13+
def __init__(self) -> None:
14+
self._condition = threading.Condition()
15+
self._is_delivered = False
16+
self._value: Optional[T] = None
17+
18+
def deliver(self, value: T):
19+
with self._condition:
20+
if not self._is_delivered:
21+
self._is_delivered = True
22+
self._value = value
23+
24+
def deref(
25+
self, timeout: Optional[float] = None, timeout_val: Optional[T] = None
26+
) -> Optional[T]:
27+
with self._condition:
28+
if self._condition.wait_for(lambda: self._is_delivered, timeout=timeout):
29+
return self._value
30+
else:
31+
return timeout_val
32+
33+
@property
34+
def is_realized(self) -> bool:
35+
with self._condition:
36+
return self._is_delivered

src/basilisp/lang/runtime.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ class Namespace:
390390
"basilisp.lang.list",
391391
"basilisp.lang.map",
392392
"basilisp.lang.multifn",
393+
"basilisp.lang.promise",
393394
"basilisp.lang.reader",
394395
"basilisp.lang.runtime",
395396
"basilisp.lang.seq",

tests/basilisp/promise_test.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import threading
2+
3+
import basilisp.lang.vector as vec
4+
from basilisp.lang.promise import Promise
5+
6+
7+
def test_promise():
8+
v = vec.v(1, 2, 3)
9+
10+
p = Promise()
11+
assert False is p.is_realized
12+
13+
def set_promise():
14+
p.deliver(v)
15+
16+
assert "not set yet" == p.deref(timeout=0.2, timeout_val="not set yet")
17+
assert False is p.is_realized
18+
19+
t = threading.Thread(target=set_promise)
20+
t.start()
21+
t.join()
22+
23+
assert v == p.deref()
24+
assert True is p.is_realized
25+
26+
p.deliver("another value")
27+
28+
assert v == p.deref()
29+
assert True is p.is_realized

0 commit comments

Comments
 (0)