Skip to content

Add Clojure's seq abstraction #32

@gilch

Description

@gilch

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions