diff --git a/CHANGELOG.md b/CHANGELOG.md index f759d369..535c60cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added a `:slots` meta flag for `deftype` to disable creation of `__slots__` on created types (#1241) * Added support for f-strings (#922) * Added the `aslice` macro to facilitate the use of Python style `array[start:stop:step]` slicing in Basilisp (#1248) + * Added the `IPending` interface which is implemented by delays, futures, and promises (#1260) ### Changed * Removed implicit support for single-use iterables in sequences, and introduced `iterator-seq` to expliciltly handle them (#1192) diff --git a/src/basilisp/core.lpy b/src/basilisp/core.lpy index 8d856e8f..b84ea152 100644 --- a/src/basilisp/core.lpy +++ b/src/basilisp/core.lpy @@ -1404,6 +1404,11 @@ [x] (and (integer? x) (neg? x))) +(defn ^:inline promise? + "Return ``true`` if ``x`` is a promise." + [x] + (instance? basilisp.lang.promise/Promise x)) + (defn ^:inline some? "Return ``true`` if ``x`` is not ``nil``\\, otherwise ``false`` s." [x] diff --git a/src/basilisp/lang/compiler/analyzer.py b/src/basilisp/lang/compiler/analyzer.py index d8bd3352..94ec4764 100644 --- a/src/basilisp/lang/compiler/analyzer.py +++ b/src/basilisp/lang/compiler/analyzer.py @@ -1031,12 +1031,10 @@ def _def_ast( # pylint: disable=too-many-locals,too-many-statements # where we directly set the Var meta for the running Basilisp instance # this causes problems since we'll end up getting something like # `(quote ([] [v]))` rather than simply `([] [v])`. - arglists_meta = def_meta.val_at(ARGLISTS_KW) # type: ignore + arglists_meta = def_meta.val_at(ARGLISTS_KW) if isinstance(arglists_meta, llist.PersistentList): assert arglists_meta.first == SpecialForm.QUOTE - var_meta = def_meta.update( # type: ignore - {ARGLISTS_KW: runtime.nth(arglists_meta, 1)} - ) + var_meta = def_meta.update({ARGLISTS_KW: runtime.nth(arglists_meta, 1)}) else: var_meta = def_meta @@ -1055,7 +1053,7 @@ def _def_ast( # pylint: disable=too-many-locals,too-many-statements var = Var.intern_unbound( ns_sym, bare_name, - dynamic=def_meta.val_at(SYM_DYNAMIC_META_KEY, False), # type: ignore + dynamic=def_meta.val_at(SYM_DYNAMIC_META_KEY, False), meta=var_meta, ) @@ -1076,7 +1074,7 @@ def _def_ast( # pylint: disable=too-many-locals,too-many-statements "generated inline function" ) var.alter_meta(lambda m: m.assoc(SYM_INLINE_META_KW, init.inline_fn)) # type: ignore[misc] - def_meta = def_meta.assoc(SYM_INLINE_META_KW, init.inline_fn.form) # type: ignore[union-attr] + def_meta = def_meta.assoc(SYM_INLINE_META_KW, init.inline_fn.form) if tag_ast is not None and any( arity.tag is not None for arity in init.arities @@ -1113,7 +1111,7 @@ def _def_ast( # pylint: disable=too-many-locals,too-many-statements # some-name # "some value") meta_ast = _analyze_form( - def_meta.update( # type: ignore + def_meta.update( { NAME_KW: llist.l(SpecialForm.QUOTE, bare_name), NS_KW: llist.l( diff --git a/src/basilisp/lang/delay.py b/src/basilisp/lang/delay.py index 54e920bb..03270e70 100644 --- a/src/basilisp/lang/delay.py +++ b/src/basilisp/lang/delay.py @@ -3,7 +3,7 @@ import attr from basilisp.lang import atom as atom -from basilisp.lang.interfaces import IDeref +from basilisp.lang.interfaces import IDeref, IPending T = TypeVar("T") @@ -15,7 +15,7 @@ class _DelayState(Generic[T]): computed: bool = False -class Delay(IDeref[T]): +class Delay(IDeref[T], IPending): __slots__ = ("_state",) def __init__(self, f: Callable[[], T]) -> None: diff --git a/src/basilisp/lang/futures.py b/src/basilisp/lang/futures.py index 4417ffab..9830212b 100644 --- a/src/basilisp/lang/futures.py +++ b/src/basilisp/lang/futures.py @@ -7,14 +7,14 @@ import attr from typing_extensions import ParamSpec -from basilisp.lang.interfaces import IBlockingDeref +from basilisp.lang.interfaces import IBlockingDeref, IPending T = TypeVar("T") P = ParamSpec("P") @attr.frozen(eq=True, repr=False) -class Future(IBlockingDeref[T]): +class Future(IBlockingDeref[T], IPending): _future: "_Future[T]" def __repr__(self): # pragma: no cover diff --git a/src/basilisp/lang/interfaces.py b/src/basilisp/lang/interfaces.py index 91beb1fd..3c533d7d 100644 --- a/src/basilisp/lang/interfaces.py +++ b/src/basilisp/lang/interfaces.py @@ -308,6 +308,22 @@ def val_at(self, k: K, default: Optional[V] = None) -> Optional[V]: raise NotImplementedError() +class IPending(ABC): + """``IPending`` types are types which may represent deferred computations such + as a future or promise. + + .. seealso:: + + :lpy:fn:`realized?`, :lpy:fn:`delay`, :lpy:fn:`future`, :lpy:fn:`promise`""" + + __slots__ = () + + @property + @abstractmethod + def is_realized(self) -> bool: + raise NotImplementedError() + + class IPersistentCollection(ISeqable[T]): """``IPersistentCollection`` types support both fetching empty variants of an existing persistent collection and creating a new collection with additional diff --git a/src/basilisp/lang/promise.py b/src/basilisp/lang/promise.py index 4168d0b1..4af62067 100644 --- a/src/basilisp/lang/promise.py +++ b/src/basilisp/lang/promise.py @@ -1,12 +1,12 @@ import threading from typing import Optional, TypeVar -from basilisp.lang.interfaces import IBlockingDeref +from basilisp.lang.interfaces import IBlockingDeref, IPending T = TypeVar("T") -class Promise(IBlockingDeref[T]): +class Promise(IBlockingDeref[T], IPending): __slots__ = ("_condition", "_is_delivered", "_value") def __init__(self) -> None: diff --git a/src/basilisp/lang/reader.py b/src/basilisp/lang/reader.py index 2de2d7de..1a4692d3 100644 --- a/src/basilisp/lang/reader.py +++ b/src/basilisp/lang/reader.py @@ -812,7 +812,7 @@ def _read_map( k = process_key(k) try: if k in d: - raise ctx.syntax_error(f"Duplicate key '{k}' in map literal") # type: ignore[str-bytes-safe] + raise ctx.syntax_error(f"Duplicate key '{k}' in map literal") except TypeError as e: raise ctx.syntax_error("Map keys must be hashable") from e else: @@ -1622,10 +1622,7 @@ def _read_reader_conditional(ctx: ReaderContext) -> LispReaderForm: reader_cond = _read_reader_conditional_preserving(ctx, is_splicing) if ctx.should_process_reader_cond and not reader_cond.is_splicing: form = _select_reader_conditional_branch(ctx, reader_cond) - return cast( - LispReaderForm, - COMMENT if form is ReaderConditional.FEATURE_NOT_PRESENT else form, - ) + return COMMENT if form is ReaderConditional.FEATURE_NOT_PRESENT else form else: return reader_cond