• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

basilisp-lang / basilisp / 7379247703

01 Jan 2024 06:37PM UTC coverage: 99.008%. Remained the same
7379247703

push

github

web-flow
callable var (#768)

Hi,

could you please review compatibility patch with Clojure to make vars
callable. It addresses #767.

I am not sure this if the `class Var` is the right place to make vars
callable or the analyzer, which should expand them to a callable var
value last node.

Nevertheless, I will kindly request your help with the type hinting,
currently it will fail the linter with the following error

```
src/basilisp/lang/runtime.py:279:15: E1102: self.value is not callable (not-callable)
```

but not sure how to fix it, I've tried the class Var `value` property
method to return a maybe Callable but it didn't work.

Thanks

Co-authored-by: ikappaki <ikappaki@users.noreply.github.com>

1691 of 1693 branches covered (0.0%)

Branch coverage included in aggregate %.

7892 of 7986 relevant lines covered (98.82%)

0.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.25
/src/basilisp/lang/seq.py
1
import functools
1✔
2
from typing import Any, Callable, Iterable, Iterator, Optional, TypeVar
1✔
3

4
from basilisp.lang.interfaces import (
1✔
5
    IPersistentMap,
6
    ISeq,
7
    ISeqable,
8
    ISequential,
9
    IWithMeta,
10
)
11
from basilisp.util import Maybe
1✔
12

13
T = TypeVar("T")
1✔
14

15

16
class _EmptySequence(IWithMeta, ISequential, ISeq[T]):
1✔
17
    __slots__ = ("_meta",)
1✔
18

19
    def __init__(self, meta: Optional[IPersistentMap] = None):
1✔
20
        self._meta = meta
1✔
21

22
    def __repr__(self):
23
        return "()"
24

25
    def __bool__(self):
1✔
26
        return True
1✔
27

28
    def seq(self) -> Optional[ISeq[T]]:
1✔
29
        return None
×
30

31
    @property
1✔
32
    def meta(self) -> Optional[IPersistentMap]:
1✔
33
        return self._meta
×
34

35
    def with_meta(self, meta: Optional[IPersistentMap]) -> "_EmptySequence[T]":
1✔
36
        return _EmptySequence(meta=meta)
×
37

38
    @property
1✔
39
    def is_empty(self) -> bool:
1✔
40
        return True
1✔
41

42
    @property
1✔
43
    def first(self) -> Optional[T]:
1✔
44
        return None
1✔
45

46
    @property
1✔
47
    def rest(self) -> ISeq[T]:
1✔
48
        return self
1✔
49

50
    def cons(self, elem: T) -> ISeq[T]:
1✔
51
        return Cons(elem, self)
1✔
52

53

54
EMPTY: ISeq = _EmptySequence()
1✔
55

56

57
class Cons(ISeq[T], ISequential, IWithMeta):
1✔
58
    __slots__ = ("_first", "_rest", "_meta")
1✔
59

60
    def __init__(
1✔
61
        self,
62
        first,
63
        seq: Optional[ISeq[T]] = None,
64
        meta: Optional[IPersistentMap] = None,
65
    ) -> None:
66
        self._first = first
1✔
67
        self._rest = Maybe(seq).or_else_get(EMPTY)
1✔
68
        self._meta = meta
1✔
69

70
    @property
1✔
71
    def is_empty(self) -> bool:
1✔
72
        return False
1✔
73

74
    @property
1✔
75
    def first(self) -> Optional[T]:
1✔
76
        return self._first
1✔
77

78
    @property
1✔
79
    def rest(self) -> ISeq[T]:
1✔
80
        return self._rest
1✔
81

82
    def cons(self, elem: T) -> "Cons[T]":
1✔
83
        return Cons(elem, self)
1✔
84

85
    @property
1✔
86
    def meta(self) -> Optional[IPersistentMap]:
1✔
87
        return self._meta
1✔
88

89
    def with_meta(self, meta: Optional[IPersistentMap]) -> "Cons[T]":
1✔
90
        return Cons(self._first, seq=self._rest, meta=meta)
×
91

92

93
class _Sequence(IWithMeta, ISequential, ISeq[T]):
1✔
94
    """Sequences are a thin wrapper over Python Iterable values so they can
95
    satisfy the Basilisp `ISeq` interface.
96

97
    Sequences are singly linked lists which lazily traverse the input Iterable.
98

99
    Do not directly instantiate a Sequence. Instead use the `sequence` function
100
    below."""
101

102
    __slots__ = ("_first", "_seq", "_rest", "_meta")
1✔
103

104
    def __init__(
1✔
105
        self, s: Iterator[T], first: T, *, meta: Optional[IPersistentMap] = None
106
    ) -> None:
107
        self._seq = s
1✔
108
        self._first = first
1✔
109
        self._rest: Optional[ISeq] = None
1✔
110
        self._meta = meta
1✔
111

112
    @property
1✔
113
    def meta(self) -> Optional[IPersistentMap]:
1✔
114
        return self._meta
1✔
115

116
    def with_meta(self, meta: Optional[IPersistentMap]) -> "_Sequence[T]":
1✔
117
        return _Sequence(self._seq, self._first, meta=meta)
1✔
118

119
    @property
1✔
120
    def is_empty(self) -> bool:
1✔
121
        return False
1✔
122

123
    @property
1✔
124
    def first(self) -> Optional[T]:
1✔
125
        return self._first
1✔
126

127
    @property
1✔
128
    def rest(self) -> "ISeq[T]":
1✔
129
        if self._rest:
1✔
130
            return self._rest
1✔
131

132
        try:
1✔
133
            n = next(self._seq)
1✔
134
            self._rest = _Sequence(self._seq, n)
1✔
135
        except StopIteration:
1✔
136
            self._rest = EMPTY
1✔
137

138
        return self._rest
1✔
139

140
    def cons(self, elem):
1✔
141
        return Cons(elem, self)
1✔
142

143

144
LazySeqGenerator = Callable[[], Optional[ISeq[T]]]
1✔
145

146

147
class LazySeq(IWithMeta, ISequential, ISeq[T]):
1✔
148
    """LazySeqs are wrappers for delaying sequence computation. Create a LazySeq
149
    with a function that can either return None or a Seq. If a Seq is returned,
150
    the LazySeq is a proxy to that Seq.
151

152
    Callers should never provide the `obj` or `seq` arguments -- these are provided
153
    only to support `with_meta` returning a new LazySeq instance."""
154

155
    __slots__ = ("_gen", "_obj", "_seq", "_meta")
1✔
156

157
    def __init__(
1✔
158
        self,
159
        gen: Optional[LazySeqGenerator],
160
        obj: Optional[ISeq[T]] = None,
161
        seq: Optional[ISeq[T]] = None,
162
        *,
163
        meta: Optional[IPersistentMap] = None,
164
    ) -> None:
165
        self._gen: Optional[LazySeqGenerator] = gen
1✔
166
        self._obj: Optional[ISeq[T]] = obj
1✔
167
        self._seq: Optional[ISeq[T]] = seq
1✔
168
        self._meta = meta
1✔
169

170
    @property
1✔
171
    def meta(self) -> Optional[IPersistentMap]:
1✔
172
        return self._meta
1✔
173

174
    def with_meta(self, meta: Optional[IPersistentMap]) -> "LazySeq[T]":
1✔
175
        return LazySeq(self._gen, obj=self._obj, seq=self._seq, meta=meta)
×
176

177
    # LazySeqs have a fairly complex inner state, in spite of the simple interface.
178
    # Calls from Basilisp code should be providing the only generator seed function.
179
    # Calls to `(seq ...)` cause the LazySeq to cache the generator function locally
180
    # (as explained in _compute_seq), clear the _gen attribute, and cache the results
181
    # of that generator function call as _obj. _obj may be None, some other ISeq, or
182
    # perhaps another LazySeq. Finally, the LazySeq attempts to consume all returned
183
    # LazySeq objects before calling `(seq ...)` on the result, which is cached in the
184
    # _seq attribute.
185

186
    def _compute_seq(self) -> Optional[ISeq[T]]:
1✔
187
        if self._gen is not None:
1✔
188
            # This local caching of the generator function and clearing of self._gen
189
            # is absolutely critical for supporting co-recursive lazy sequences.
190
            #
191
            # The original example that prompted this change is below:
192
            #
193
            #   (def primes (remove
194
            #                (fn [x] (some #(zero? (mod x %)) primes))
195
            #                (iterate inc 2)))
196
            #
197
            #   (take 5 primes)  ;; => stack overflow
198
            #
199
            # If we don't clear self._gen, each successive call to (some ... primes)
200
            # will end up forcing the primes LazySeq object to call self._gen, rather
201
            # than caching the results, allowing examination of the partial seq
202
            # computed up to that point.
203
            gen = self._gen
1✔
204
            self._gen = None
1✔
205
            self._obj = gen()
1✔
206
        return self._obj if self._obj is not None else self._seq
1✔
207

208
    def seq(self) -> Optional[ISeq[T]]:
1✔
209
        self._compute_seq()
1✔
210
        if self._obj is not None:
1✔
211
            o = self._obj
1✔
212
            self._obj = None
1✔
213
            # Consume any additional lazy sequences returned immediately so we have a
214
            # "real" concrete sequence to proxy to.
215
            #
216
            # The common idiom with LazySeqs is to return (cons value (lazy-seq ...))
217
            # from the generator function, so this will only result in evaluating away
218
            # instances where _another_ LazySeq is returned rather than a cons cell
219
            # with a concrete first value. This loop will not consume the LazySeq in
220
            # the rest position of the cons.
221
            while isinstance(o, LazySeq):
1✔
222
                o = o._compute_seq()  # type: ignore
1✔
223
            self._seq = to_seq(o)
1✔
224
        return self._seq
1✔
225

226
    @property
1✔
227
    def is_empty(self) -> bool:
1✔
228
        return self.seq() is None
1✔
229

230
    @property
1✔
231
    def first(self) -> Optional[T]:
1✔
232
        try:
1✔
233
            return self.seq().first  # type: ignore[union-attr]
1✔
234
        except AttributeError:
1✔
235
            return None
1✔
236

237
    @property
1✔
238
    def rest(self) -> "ISeq[T]":
1✔
239
        try:
1✔
240
            return self.seq().rest  # type: ignore[union-attr]
1✔
241
        except AttributeError:
1✔
242
            return EMPTY
1✔
243

244
    def cons(self, elem):
1✔
245
        return Cons(elem, self)
1✔
246

247
    @property
1✔
248
    def is_realized(self):
1✔
249
        return self._gen is None
1✔
250

251

252
def sequence(s: Iterable) -> ISeq[Any]:
1✔
253
    """Create a Sequence from Iterable s."""
254
    try:
1✔
255
        i = iter(s)
1✔
256
        return _Sequence(i, next(i))
1✔
257
    except StopIteration:
1✔
258
        return EMPTY
1✔
259

260

261
def _seq_or_nil(s: Optional[ISeq]) -> Optional[ISeq]:
1✔
262
    """Return None if a ISeq is empty, the ISeq otherwise."""
263
    if s is None or s.is_empty:
1✔
264
        return None
1✔
265
    return s
1✔
266

267

268
@functools.singledispatch
1✔
269
def to_seq(o) -> Optional[ISeq]:
1✔
270
    """Coerce the argument o to a ISeq. If o is None, return None."""
271
    return _seq_or_nil(sequence(o))
1✔
272

273

274
@to_seq.register(type(None))
1✔
275
def _to_seq_none(_) -> None:
1✔
276
    return None
1✔
277

278

279
@to_seq.register(ISeq)
1✔
280
def _to_seq_iseq(o: ISeq) -> Optional[ISeq]:
1✔
281
    return _seq_or_nil(o)
1✔
282

283

284
@to_seq.register(LazySeq)
1✔
285
def _to_seq_lazyseq(o: LazySeq) -> Optional[ISeq]:
1✔
286
    # Force evaluation of the LazySeq by calling o.seq() directly.
287
    return o.seq()
1✔
288

289

290
@to_seq.register(ISeqable)
1✔
291
def _to_seq_iseqable(o: ISeqable) -> Optional[ISeq]:
1✔
292
    return _seq_or_nil(o.seq())
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc