We're running into occasional annoying problems because the ubiquitous Python iterators are mutable. Clojure doesn't have this problem because it uses the immutable seq abstraction instead. A seq can still have the lazyness of some iterables, but getting the next item won't change what the first seq means.
I've been playing around with a Python implementation of seq. Here's a rough attempt. This could easily be translated to Hy.
from collections.abc import Iterable
class LazyCons:
__slots__ = ['car', 'cdr']
def __init__(self, car, cdr):
self.car = lambda: car
def lazy_cdr():
res = cdr()
self.cdr = lambda: res
return res
self.cdr = lazy_cdr
class Seq(LazyCons, Iterable):
__slots__ = ()
def __init__(self, iterable):
it = iter(iterable)
car = next(it)
super().__init__(car, lambda: Seq(it))
def __iter__(self):
def iterator():
seq = self
while 1:
yield seq.car()
seq = seq.cdr()
return iterator()
And a function to demonstrate the laziness.
def verboseRange(*stop_startstopstep):
for i in range(*stop_startstopstep):
print('yielding', i)
yield i
And the demonstration:
>>> spam = Seq(iter(verboseRange(5))) # note iter() call
yielding 0
>>> spam.car()
0
>>> spam.car()
0
>>> spam.cdr()
yielding 1
<__main__.Seq object at 0x0000019C4D773D68>
>>> spam.cdr().car()
1
>>> list(spam)
yielding 2
yielding 3
yielding 4
[0, 1, 2, 3, 4]
>>> spam.car()
0
That iter() call isn't required, but it demonstrates converting a mutable iterator into an immutable seq (provided nothing else has a reference to that iterator).
Some more ideas:
Clojure often converts collections to seq's automatically. Rather than add that to a lot of Hy functions, we might instead have a short reader macro to do it. Although, (seq xs) is not that long to begin with.
We could also change the __repr__ of a Seq to print off up to the first 20 items or something. This would make seq's a lot more friendly in the repl, and lazy Python iterables too especially, when combined with the reader macro.
We're running into occasional annoying problems because the ubiquitous Python iterators are mutable. Clojure doesn't have this problem because it uses the immutable seq abstraction instead. A seq can still have the lazyness of some iterables, but getting the next item won't change what the first seq means.
I've been playing around with a Python implementation of seq. Here's a rough attempt. This could easily be translated to Hy.
And a function to demonstrate the laziness.
And the demonstration:
That
iter()call isn't required, but it demonstrates converting a mutable iterator into an immutable seq (provided nothing else has a reference to that iterator).Some more ideas:
Clojure often converts collections to seq's automatically. Rather than add that to a lot of Hy functions, we might instead have a short reader macro to do it. Although,
(seq xs)is not that long to begin with.We could also change the
__repr__of aSeqto print off up to the first 20 items or something. This would make seq's a lot more friendly in the repl, and lazy Python iterables too especially, when combined with the reader macro.