picl
2024-10-12
Python Itertools in Common Lisp
picl 
Anish Moorthy anlsh@protonmail.com
Python Itertools in Common Lisp (v1.0.0). Pronounced like "pickle"
A (very nearly) complete port of Python's itertools package, complete with laziness where applicable.
This project currently lives on Github. Pull requests welcome!
Objectives and Rationale
PICL aims to provide a complete port of itertools, complete with laziness,
without any reliance on cl-cont.
cl-itertools
and snakes, provide similar functionality.
Unfortunately both libraries rely on cl-cont, meaning they wont always play
nice with the condition system, and cl-itertools remains very incomplete on
top of that
Installation
PICL is in Quicklisp, and can be installed as follows
(ql:quickload :picl)
Do not :use this package: it might export new symbols in the future. You have
been forewarned.
Documentation
Thanks to Staple you can read the documentation online or build it yourself like so
(staple:generate :picl :if-exists :supersede)
If you don't have PICL's dependencies loaded into your image yet, you'll get some harmless warnings about invalid definitions
Testing
A fairly comprehensive test suite written with FiveAM is provided. You can run it yourself either manually or through asdf
;; The easy way (asdf:test-system :picl) ;; The slightly less easy way (ql:quickload :picl/tests) (fiveam:run! 'picl/tests:suite)
Concepts and How-To
An "iterator" in PICL is simply a thunk producing two values: the payload and the alive-indicator. The alive-indicator should be truthy until after the iterator is consumed.
By example
(let ((it (make-iterator '(1 2)))) (next it) ;; (values 1 t) (next it) ;; (values 2 t) (next it)) ;; (values nil nil)
After returning nil, all further next calls should also produce nil as
quickly as possible. Furthermore when the alive indicator is nil, the payload
should be ignored.
To create iterators over your own objects, specialize the make-iterator
generic function appropriately. For instance, the make-iterator definition for
lists is
(defmethod make-iterator ((obj list)) (lambda () (if obj (values (prog1 (car obj) (setf obj (cdr obj))) t) (values nil nil))))
Specializations for lists and vectors are predefined. A universal in-it
driver is also provided for Iterate
through the picl/iterate system.
(ql:quickload '(:picl :picl/iterate)) ;; The "iterate" package has been :use'd here (iterate (for i in-it (picl:permutations '(1 2 3))) (collect i)) ;; (#(1 2 3) #(1 3 2) #(2 1 3) #(2 3 1) #(3 1 2) #(3 2 1))
Note: All of the combinatoric iterators produce vectors, which can be
annoying because those are second-class citizens in CL (you can't destructure
them, for instance). To get around this, you can wrap the iterator in
(picl:map #'iter-to-list <>)
(picl:iter-to-list (picl:map #'picl:iter-to-list (picl:permutations '(1 2 3)))) ;; ((1 2 3) (1 3 2) (2 1 3) (2 3 1) (3 1 2) (3 2 1))
It's a bit clunky for sure, so in the future I might extend the in-it
clause to perform conversions like this when specified
Future Work
Functions still missing from Python's itertools (due to laziness: if you need these drop an issue/PR and I'll get around to implementing them)
Extensions to library
- Port the more-itertools recipes found at bottom of the Python itertools
package
- Port the more-iterools package
(seems like a big job)
- Some sort of integration with fset's
sequence type?
License
This project is provided under the MIT License (see LICENSE.md)