git.fiddlerwoaroof.com
Raw Blame History
(defpackage :fwoar.transduce
  (:use :cl )
  (:shadow :map :filter)
  (:export #:map
           #:filter
           #:take
           #:compress
           #:build-list
           #:build-array
           #:emitting
           #:build
           #:with-transducer-arities))
(in-package :fwoar.transduce)

(declaim (inline build-list build-array emitting map filter take))

(defmacro with-transducer-arities ((accum it) &body body)
  (alexandria:with-gensyms (a-p i-p)
    `(lambda (rf)
       (declare (type (function (&optional t t) t) rf))
       (lambda (&optional (,accum nil ,a-p) (,it nil ,i-p))
         (declare (optimize (speed 3) (safety 1) (debug 0))
                  (dynamic-extent ,it))
         (cond
           (,i-p ,@body)
           (,a-p (funcall rf ,accum))
           (t (funcall rf)))))))

(defun map (mapping-fun)
  (with-transducer-arities (accum it)
    (let ((mapping-fun (alexandria:ensure-function mapping-fun)))
      (funcall rf
               accum
               (funcall mapping-fun
                        it)))))

(defun filter (filtering-fun)
  (with-transducer-arities (accum it)
    (let ((filtering-fun (alexandria:ensure-function filtering-fun)))
      (if (funcall filtering-fun it)
          (funcall rf accum it)
          accum))))

(defun take (count)
  (let ((idx 0))
    (with-transducer-arities (accum it)
      (prog1 (if (< idx count)
                 (funcall rf accum it)
                 (funcall rf accum))
        (incf idx)))))

(defun compress (&optional (test 'eql))
  (let ((v (gensym)))
    (with-transducer-arities (accum it)
      (prog1 (if (funcall test v it)
                 accum
                 (funcall rf accum it))
        (setf v it)))))

(defstruct %concat-tree
  head
  tail)
(defun %flatten-concat-tree (tree)
  (if (null (%concat-tree-head tree))
      (%concat-tree-tail tree)
      (let* ((head (%concat-tree-head tree))
             (head-tail (%concat-tree-tail head)))
        (setf (%concat-tree-tail head)
              (if head-tail
                  (cons head-tail (%concat-tree-tail tree))
                  (%concat-tree-tail tree)))
        (%flatten-concat-tree head))))

(defun build-list (rf)
  (declare (type (function (&optional t t) t) rf)
           (dynamic-extent rf))
  (lambda (&optional (a nil a-p) (i nil i-p))
    (declare (optimize (speed 3) (safety 1) (debug 0)))
    (cond
      (i-p (make-%concat-tree :head a :tail i))
      (a-p (funcall rf (%flatten-concat-tree
                        (make-%concat-tree :head a :tail nil))))
      (t (make-%concat-tree :head nil :tail nil)))))

(defun build-list-mutating (rf)
  (declare (type (function (&optional t t) t) rf)
           (dynamic-extent rf))
  (let ((l (list nil nil)))
    (declare (optimize (speed 3) (safety 1) (debug 0)))
    (lambda (&optional (a nil a-p) (i nil i-p))
      (cond
        (i-p (cdr (rplacd a (list i))))
        (a-p (funcall rf (cdr l)))
        (t l)))))

(defun build-array (rf)
  (declare (type (function (&optional t t) t) rf)
           (dynamic-extent rf))
  (let ((l (make-array 1000 :adjustable t :fill-pointer 0)))
    (lambda (&optional (a nil a-p) (i nil i-p))
      (declare (optimize (speed 3) (safety 1) (debug 0)))
      (cond
        (i-p (vector-push-extend i a)
             a)
        (a-p (funcall rf l))
        (t l)))))

(defun emitting (rf)
  (lambda (&optional (a nil a-p) (i nil i-p))
    (cond
      (i-p (format t "~&~s~%" i)
           a)
      (a-p (funcall rf :done))
      (t :going))))

(defun build (builder xf input)
  (locally (declare (optimize (debug 3)))
    (block nil
      (let ((rf (lambda (&optional (a nil a-p) (i nil i-p))
                  (declare (ignore i))
                  (cond
                    (i-p (error "this shouldn't happen"))
                    (a-p (return a))
                    (t (error "this shouldn't happen."))))))
        (etypecase input
          (list (loop with sub-fun = (funcall xf
                                              (funcall builder rf))
                      for next in input
                      for accum = (funcall sub-fun (funcall sub-fun) next)
                        then (funcall sub-fun accum next)
                      finally (funcall sub-fun accum)))
          (vector (loop with sub-fun = (funcall xf
                                                (funcall builder rf))
                        for next across input
                        for accum = (funcall sub-fun (funcall sub-fun) next)
                          then (funcall sub-fun accum next)
                        finally (funcall sub-fun accum)))
          (sequence
           (loop with sub-fun = (funcall xf
                                         (funcall builder rf))
                 with sequence = input
                 for (iterator limit from-end)
                   = (multiple-value-list (sb-sequence:make-sequence-iterator sequence))
                     then (list (sb-sequence:iterator-step sequence iterator from-end) limit from-end)
                 for accum
                   = (funcall sub-fun)
                     then (funcall sub-fun accum next)
                 until (sb-sequence:iterator-endp sequence iterator limit from-end)
                 for next = (sb-sequence:iterator-element sequence iterator)
                 finally (funcall sub-fun accum))))))))

(defun tmp ()
  (build 'build-array
         (alexandria:compose (map '1+)
                             (map (lambda (v)
                                    (* v 3)))
                             (filter 'oddp))
         #(1 2 3 4)))