Skip to content

Commit

Permalink
Functional iterators! Fun!!
Browse files Browse the repository at this point in the history
  • Loading branch information
Scott L. Burson committed Feb 23, 2025
1 parent a5a7ec5 commit 3bf289f
Show file tree
Hide file tree
Showing 4 changed files with 584 additions and 8 deletions.
4 changes: 4 additions & 0 deletions Code/defs.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
#:? ; for `query' on 'list-relation'
;; Used by the bag methods that convert to and from lists.
#:alist
;; Miscellaneous GMap arg types
#:fun-sequence #:fun-bag-pairs #:fun-map
;; Miscellaneous GMap result types
#:map-to-sets #:append-unique
;; Bounded sets
Expand Down Expand Up @@ -189,6 +191,8 @@
#:? ; for `query' on 'list-relation'
;; Used by the bag methods that convert to and from lists.
#:alist
;; Miscellaneous GMap arg types
#:fun-sequence #:fun-bag-pairs #:fun-map
;; Miscellaneous GMap result types
#:map-to-sets #:append-unique
;; Bounded sets
Expand Down
87 changes: 79 additions & 8 deletions Code/fset.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -462,14 +462,30 @@ take additional keyword arguments to further specify the kind of conversion."))
;;; to check termination; if it might contain `nil', you can use the extra value.
(defgeneric iterator (collection &key)
(:documentation
"Returns an iterator for the collection. (These are stateful iterators and
are not thread-safe; if you want a pure iterator, your best bet is to `convert'
the collection to a list.) The iterator is a closure of one argument; given
`:done?', it returns true iff the iterator is exhausted; given `:more?', it
returns true iff the iterator is _not_ exhausted. Given `:get', if the iterator
is not exhausted, it returns the next element (or pair, for a map, as two values),
with the second value (third, for a map) being true, and advances one element; if
it is exhausted, it returns two `nil' values (three, for a map)."))
"Returns an iterator for the collection. \(These are stateful iterators and
are not thread-safe; if you want a pure iterator, see `fun-iterator'.\) The iterator
is a function of one argument; given `:done?', it returns true iff the iterator is
exhausted; given `:more?', it returns true iff the iterator is _not_ exhausted.
Given `:get', if the iterator is not exhausted, it returns the next element (or
pair, for a map, as two values), with the second value (third, for a map) being
true, and advances one element; if it is exhausted, it returns two `nil' values
\(three, for a map\).
The bag method takes a `pairs?' keyword argument; if true, it returns each element
only once, with its multiplicity as the second value, as for a map."))

(defgeneric fun-iterator (collection &key from-end?)
(:documentation
"Returns a functional iterator for the collection. \(These iterators are
thread-safe.\) The iterator is a function of one argument; given `:empty?', it
returns true iff the iterator is exhausted; given `:more?', it returns true iff
the iterator is _not_ exhausted. Given `:first', if it is not exhausted, it
returns the next element \(or pair, for a map, as two values\), with an additional
true value; if it is exhausted, it returns two or three `nil' values.
If `from-end?' is true, the collection is iterated in reverse order. The bag
method also takes a `pairs?' keyword argument; if true, it returns each element
only once, with its multiplicity as the second value, as for a map."))

;;; The `&allow-other-keys' is to persuade SBCL not to issue warnings about keywords
;;; that are accepted by some methods of `iterator'.
Expand Down Expand Up @@ -546,6 +562,19 @@ as an FSet seq, or a set or bag as well."
#'(lambda (it) (declare (type function it)) (funcall it ':done?))
#'(lambda (it) (declare (type function it)) (funcall it ':get))))

;;; The new (for 1.4.7) functional iterators all take a `:from-end?' option.
;;; Other than that, there's no reason to use them with `gmap'; they're slower
;;; (though not as much slower as you might expect; less than 2x) and generate
;;; lots of garbage. They were fun to write, though :-)
(gmap:def-arg-type fun-sequence (fun-iterable &key from-end?)
"Yields the elements of `fun-iterable', which can be an FSet seq, set, or bag.
If `:from-end?' is true, iterates in reverse order."
`((fun-iterator ,fun-iterable :from-end? ,from-end?)
#'(lambda (it) (funcall it ':empty?))
#'(lambda (it) (funcall it ':first))
#'(lambda (it) (funcall it ':rest))))


;;; ================================================================================
;;; Generic versions of Common Lisp sequence functions

Expand Down Expand Up @@ -1235,6 +1264,11 @@ for the possibility of different set implementations; it is not for public use.
(defmethod iterator ((s wb-set) &key)
(Make-WB-Set-Tree-Iterator (wb-set-contents s)))

(defmethod fun-iterator ((s wb-set) &key from-end?)
(if from-end?
(WB-Set-Tree-Rev-Fun-Iter (wb-set-contents s))
(WB-Set-Tree-Fun-Iter (wb-set-contents s))))

(defmethod filter ((pred function) (s wb-set))
(wb-set-filter pred s))

Expand Down Expand Up @@ -1853,6 +1887,15 @@ different bag implementations; it is not for public use. `elt-fn' and
(Make-WB-Bag-Tree-Pair-Iterator (wb-bag-contents b))
(Make-WB-Bag-Tree-Iterator (wb-bag-contents b))))

(defmethod fun-iterator ((s wb-bag) &key pairs? from-end?)
(if pairs?
(if from-end?
(WB-Bag-Tree-Pair-Rev-Fun-Iter (wb-bag-contents s))
(WB-Bag-Tree-Pair-Fun-Iter (wb-bag-contents s)))
(if from-end?
(WB-Bag-Tree-Rev-Fun-Iter (wb-bag-contents s))
(WB-Bag-Tree-Fun-Iter (wb-bag-contents s)))))

(defmethod filter ((pred function) (b bag))
(bag-filter pred b))

Expand Down Expand Up @@ -2133,6 +2176,12 @@ of which may be repeated."
#'WB-Bag-Tree-Pair-Iterator-Done?
(:values 2 #'WB-Bag-Tree-Pair-Iterator-Get)))

(gmap:def-arg-type fun-bag-pairs (bag &key from-end?)
`((fun-iterator ,bag :pairs? t :from-end? ,from-end?)
#'(lambda (it) (funcall it ':empty?))
(:values 2 #'(lambda (it) (funcall it ':first)))
#'(lambda (it) (funcall it ':rest))))

(gmap:def-gmap-res-type bag (&key filterp)
"Returns a bag of the values, optionally filtered by `filterp'."
`(nil #'WB-Bag-Tree-With #'make-wb-bag ,filterp))
Expand Down Expand Up @@ -2309,6 +2358,11 @@ symbols."))
(defmethod iterator ((m wb-map) &key)
(Make-WB-Map-Tree-Iterator (wb-map-contents m)))

(defmethod fun-iterator ((s wb-map) &key from-end?)
(if from-end?
(WB-Map-Tree-Rev-Fun-Iter (wb-map-contents s))
(WB-Map-Tree-Fun-Iter (wb-map-contents s))))

(defmethod filter ((pred function) (m wb-map))
(wb-map-filter pred m))

Expand Down Expand Up @@ -2439,6 +2493,12 @@ symbols."))
(push (funcall pair-fn key val) result))
(nreverse result)))

(defmethod convert ((to-type (eql 'alist)) (m map) &key)
(let ((result nil))
(do-map (key val m)
(push (cons key val) result))
(nreverse result)))

(defmethod convert ((to-type (eql 'seq)) (m map) &key (pair-fn #'cons))
(convert to-type (convert 'list m :pair-fn pair-fn)))

Expand Down Expand Up @@ -2607,6 +2667,12 @@ symbols."))
#'WB-Map-Tree-Iterator-Done?
(:values 2 #'WB-Map-Tree-Iterator-Get)))

(gmap:def-arg-type fun-map (map &key from-end?)
`((fun-iterator ,map :from-end? ,from-end?)
#'(lambda (it) (funcall it ':empty?))
(:values 2 #'(lambda (it) (funcall it ':first)))
#'(lambda (it) (funcall it ':rest))))

(gmap:def-gmap-res-type map (&key filterp default)
"Consumes two values from the mapped function; returns a map of the pairs.
Note that `filterp', if supplied, must take two arguments."
Expand Down Expand Up @@ -3038,6 +3104,11 @@ not symbols."))
(defmethod iterator ((s wb-seq) &key)
(Make-WB-Seq-Tree-Iterator (wb-seq-contents s)))

(defmethod fun-iterator ((s wb-seq) &key from-end?)
(if from-end?
(WB-Seq-Tree-Rev-Fun-Iter (wb-seq-contents s))
(WB-Seq-Tree-Fun-Iter (wb-seq-contents s))))

(defmethod domain-contains? ((s seq) x)
(and (integerp x) (>= x 0) (< x (size s))))

Expand Down
30 changes: 30 additions & 0 deletions Code/testing.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -2420,6 +2420,12 @@
(error "Set iterator failed (fs0) on iteration ~D" i))
(unless (equal? fs1 (gmap (:result set) nil (:arg list (convert 'list fs1))))
(error "Set iterator or accumulator failed (fs1) on iteration ~D" i))
(unless (equal? (gmap (:result list) nil (:arg fun-sequence fs0))
(convert 'list fs0))
(error "Set fun-iterator failed on iteration ~D" i))
(unless (equal? (gmap (:result list) nil (:arg fun-sequence fs0 :from-end? t))
(reverse (convert 'list fs0)))
(error "Set rev-fun-iterator failed on iteration ~D" i))
(let ((fsu (union fs0 fs1))
(su (cl:union s0 s1 :test #'equal?)))
(unless (and (verify fsu) (equal? fsu (convert 'set su)))
Expand Down Expand Up @@ -2549,6 +2555,12 @@
(error "Map iterator failed (fm0) on iteration ~D" i))
(unless (equal? fm1 (gmap (:result map) nil (:arg alist (convert 'list fm1))))
(error "Map iterator/accumulator failed (fm1) on iteration ~D" i))
(unless (equal? (gmap (:result alist) nil (:arg fun-map fm0))
(convert 'alist fm0))
(error "Map fun-iterator failed on iteration ~D" i))
(unless (equal? (gmap (:result alist) nil (:arg fun-map fm0 :from-end? t))
(reverse (convert 'alist fm0)))
(error "Map rev-fun-iterator failed on iteration ~D" i))
(unless (eq (Map-Compare (convert 'list fm0) m0) ':equal)
(error "Map equal? failed (fm1) on iteration ~D" i))
(unless (eq (Map-Compare (convert 'list fm1) m1) ':equal)
Expand Down Expand Up @@ -2710,6 +2722,18 @@
(error "Bag pair iterator failed (fb0) on iteration ~D" i))
(unless (equal? fb1 (gmap (:result bag-pairs) nil (:arg alist (convert 'alist fb1))))
(error "Bag pair iterator/accumulator failed (fb1) on iteration ~D" i))
(unless (equal? (gmap (:result list) nil (:arg fun-sequence fb0))
(convert 'list fb0))
(error "Bag fun-iterator failed on iteration ~D" i))
(unless (equal? (gmap (:result alist) nil (:arg fun-bag-pairs fb0))
(convert 'alist fb0))
(error "Bag pair fun-iterator failed on iteration ~D" i))
(unless (equal? (gmap (:result list) nil (:arg fun-sequence fb0 :from-end? t))
(reverse (convert 'list fb0)))
(error "Bag rev-fun-iterator failed on iteration ~D" i))
(unless (equal? (gmap (:result alist) nil (:arg fun-bag-pairs fb0 :from-end? t))
(reverse (convert 'alist fb0)))
(error "Bag pair fun-iterator failed on iteration ~D" i))
(let ((fbu (union fb0 fb1))
(bu (Alist-Bag-Union b0 b1)))
(unless (and (verify fbu) (equal? fbu (convert 'bag bu :from-type 'alist)))
Expand Down Expand Up @@ -2828,6 +2852,12 @@
(error "Seq equality failed (fs0, B), on iteration ~D" i))
(unless (gmap (:result and) #'equal? (:arg seq fs0) (:arg list s0))
(error "Seq iterator failed on iteration ~D" i))
(unless (equal? (gmap (:result list) nil (:arg fun-sequence fs0))
(convert 'list fs0))
(error "Seq fun-iterator failed on iteration ~D" i))
(unless (equal? (gmap (:result list) nil (:arg fun-sequence fs0 :from-end? t))
(reverse (convert 'list fs0)))
(error "Seq rev-fun-iterator failed on iteration ~D" i))
(unless (gmap (:result and) #'equal? (:arg seq fs0) (:arg sequence s0))
(error "Seq or list iterator failed on iteration ~D" i))
(unless (gmap (:result and) #'equal? (:arg seq fs0) (:arg sequence (coerce s0 'simple-vector)))
Expand Down
Loading

0 comments on commit 3bf289f

Please sign in to comment.