That's a neat little implementation. For bonus points, try adding these features:
1. More non-commutative operations, like Ruby's each_cons, each_slice, chunk (different than lodash's chunk). I especially enjoy implementing these both iteratively and recursively.
2. Infinite sequences (like Python's itertools.count). You may find that implementing the other operations becomes easier when you treat the sequences themselves as being lazy and potentially infinite. Do you still need to make the distinction between commutative and non-commutative operations?
3. Operations that combine multiple sequences, like iterools.zip, itertools.product, and merge (assuming sorted sequences).
4. Operations that return multiple sequences (say, a sequence of sequences). Can you implement the inverse of itertools.zip (perhaps call it unzip)?
I also especially like the use of a Symbol to halt iteration, and think it should be part of both the core language and libraries providing similar APIs (again likening to Clojure, similar to `reduced`).
My first thought was much like your second point! If we have lazy operations on collections, we’re 90% of the way to `seq` & similar lazy structures.
Edit: in fact, probably 99%. You just need to define the data structures as an initial operation that materializes values for subsequent operations on demand.
Yeah, years ago I got deep into this stuff and managed to implement a lot of it for fun, thinking I was doing something original, before discovering it’s not original (in my case, discovering Seq in OCaml). I think treating everything as lazy and potential infinite makes the implementation of operations much simpler than this article’s implementation.
I think for most usage, you’re right. There are places where it probably has more overhead than you’d want in a tight loop, and you’d be better off with the “bad” imperative version. E.g. “If a tree falls in the woods, does it make a sound?
If a pure function mutates some local data in order to produce an immutable return value, is that ok?”
I’m coming from a perspective where I now work primarily in JS/TS, prefer FP by default and with it were more accessible in the ecosystem, and even prefer `reduce` over a loop nine times out of ten. But there’s still a bail point where performance is key or “this feels incredibly awkward in this environment”.
Oh yeah, for sure there could be performance implications to lazy sequences, but ideally the compiler or interpreter could figure out some nice optimizations.
Comments
That's a neat little implementation. For bonus points, try adding these features:
1. More non-commutative operations, like Ruby's each_cons, each_slice, chunk (different than lodash's chunk). I especially enjoy implementing these both iteratively and recursively.
2. Infinite sequences (like Python's itertools.count). You may find that implementing the other operations becomes easier when you treat the sequences themselves as being lazy and potentially infinite. Do you still need to make the distinction between commutative and non-commutative operations?
3. Operations that combine multiple sequences, like iterools.zip, itertools.product, and merge (assuming sorted sequences).
4. Operations that return multiple sequences (say, a sequence of sequences). Can you implement the inverse of itertools.zip (perhaps call it unzip)?
I also especially like the use of a Symbol to halt iteration, and think it should be part of both the core language and libraries providing similar APIs (again likening to Clojure, similar to `reduced`).
My first thought was much like your second point! If we have lazy operations on collections, we’re 90% of the way to `seq` & similar lazy structures.
Edit: in fact, probably 99%. You just need to define the data structures as an initial operation that materializes values for subsequent operations on demand.
Yeah, years ago I got deep into this stuff and managed to implement a lot of it for fun, thinking I was doing something original, before discovering it’s not original (in my case, discovering Seq in OCaml). I think treating everything as lazy and potential infinite makes the implementation of operations much simpler than this article’s implementation.
I think for most usage, you’re right. There are places where it probably has more overhead than you’d want in a tight loop, and you’d be better off with the “bad” imperative version. E.g. “If a tree falls in the woods, does it make a sound? If a pure function mutates some local data in order to produce an immutable return value, is that ok?”
I’m coming from a perspective where I now work primarily in JS/TS, prefer FP by default and with it were more accessible in the ecosystem, and even prefer `reduce` over a loop nine times out of ten. But there’s still a bail point where performance is key or “this feels incredibly awkward in this environment”.
Oh yeah, for sure there could be performance implications to lazy sequences, but ideally the compiler or interpreter could figure out some nice optimizations.