git.fiddlerwoaroof.com
Browse code

feat: transducers!

Ed L authored on 31/12/2019 05:31:04
Showing 1 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,158 @@
1
+(defpackage :fwoar.transduce
2
+  (:use :cl )
3
+  (:shadow :map :filter)
4
+  (:export #:map
5
+           #:filter
6
+           #:take
7
+           #:compress
8
+           #:build-list
9
+           #:build-array
10
+           #:emitting
11
+           #:build
12
+           #:with-transducer-arities))
13
+(in-package :fwoar.transduce)
14
+
15
+(declaim (inline build-list build-array emitting map filter take))
16
+
17
+(defmacro with-transducer-arities ((accum it) &body body)
18
+  (alexandria:with-gensyms (a-p i-p)
19
+    `(lambda (rf)
20
+       (declare (type (function (&optional t t) t) rf))
21
+       (lambda (&optional (,accum nil ,a-p) (,it nil ,i-p))
22
+         (declare (optimize (speed 3) (safety 1) (debug 0))
23
+                  (dynamic-extent ,it))
24
+         (cond
25
+           (,i-p ,@body)
26
+           (,a-p (funcall rf ,accum))
27
+           (t (funcall rf)))))))
28
+
29
+(defun map (mapping-fun)
30
+  (with-transducer-arities (accum it)
31
+    (let ((mapping-fun (alexandria:ensure-function mapping-fun)))
32
+      (funcall rf
33
+               accum
34
+               (funcall mapping-fun
35
+                        it)))))
36
+
37
+(defun filter (filtering-fun)
38
+  (with-transducer-arities (accum it)
39
+    (let ((filtering-fun (alexandria:ensure-function filtering-fun)))
40
+      (if (funcall filtering-fun it)
41
+          (funcall rf accum it)
42
+          accum))))
43
+
44
+(defun take (count)
45
+  (let ((idx 0))
46
+    (with-transducer-arities (accum it)
47
+      (prog1 (if (< idx count)
48
+                 (funcall rf accum it)
49
+                 (funcall rf accum))
50
+        (incf idx)))))
51
+
52
+(defun compress (&optional (test 'eql))
53
+  (let ((v (gensym)))
54
+    (with-transducer-arities (accum it)
55
+      (prog1 (if (funcall test v it)
56
+                 accum
57
+                 (funcall rf accum it))
58
+        (setf v it)))))
59
+
60
+(defstruct %concat-tree
61
+  head
62
+  tail)
63
+(defun %flatten-concat-tree (tree)
64
+  (if (null (%concat-tree-head tree))
65
+      (%concat-tree-tail tree)
66
+      (let* ((head (%concat-tree-head tree))
67
+             (head-tail (%concat-tree-tail head)))
68
+        (setf (%concat-tree-tail head)
69
+              (if head-tail
70
+                  (cons head-tail (%concat-tree-tail tree))
71
+                  (%concat-tree-tail tree)))
72
+        (%flatten-concat-tree head))))
73
+
74
+(defun build-list (rf)
75
+  (declare (type (function (&optional t t) t) rf)
76
+           (dynamic-extent rf))
77
+  (lambda (&optional (a nil a-p) (i nil i-p))
78
+    (declare (optimize (speed 3) (safety 1) (debug 0)))
79
+    (cond
80
+      (i-p (make-%concat-tree :head a :tail i))
81
+      (a-p (funcall rf (%flatten-concat-tree
82
+                        (make-%concat-tree :head a :tail nil))))
83
+      (t (make-%concat-tree :head nil :tail nil)))))
84
+
85
+(defun build-list-mutating (rf)
86
+  (declare (type (function (&optional t t) t) rf)
87
+           (dynamic-extent rf))
88
+  (let ((l (list nil nil)))
89
+    (declare (optimize (speed 3) (safety 1) (debug 0)))
90
+    (lambda (&optional (a nil a-p) (i nil i-p))
91
+      (cond
92
+        (i-p (cdr (rplacd a (list i))))
93
+        (a-p (funcall rf (cdr l)))
94
+        (t l)))))
95
+
96
+(defun build-array (rf)
97
+  (declare (type (function (&optional t t) t) rf)
98
+           (dynamic-extent rf))
99
+  (let ((l (make-array 1000 :adjustable t :fill-pointer 0)))
100
+    (lambda (&optional (a nil a-p) (i nil i-p))
101
+      (declare (optimize (speed 3) (safety 1) (debug 0)))
102
+      (cond
103
+        (i-p (vector-push-extend i a)
104
+             a)
105
+        (a-p (funcall rf l))
106
+        (t l)))))
107
+
108
+(defun emitting (rf)
109
+  (lambda (&optional (a nil a-p) (i nil i-p))
110
+    (cond
111
+      (i-p (format t "~&~s~%" i)
112
+           a)
113
+      (a-p (funcall rf :done))
114
+      (t :going))))
115
+
116
+(defun build (builder xf input)
117
+  (locally (declare (optimize (debug 3)))
118
+    (block nil
119
+      (let ((rf (lambda (&optional (a nil a-p) (i nil i-p))
120
+                  (declare (ignore i))
121
+                  (cond
122
+                    (i-p (error "this shouldn't happen"))
123
+                    (a-p (return a))
124
+                    (t (error "this shouldn't happen."))))))
125
+        (etypecase input
126
+          (list (loop with sub-fun = (funcall xf
127
+                                              (funcall builder rf))
128
+                      for next in input
129
+                      for accum = (funcall sub-fun (funcall sub-fun) next)
130
+                        then (funcall sub-fun accum next)
131
+                      finally (funcall sub-fun accum)))
132
+          (vector (loop with sub-fun = (funcall xf
133
+                                                (funcall builder rf))
134
+                        for next across input
135
+                        for accum = (funcall sub-fun (funcall sub-fun) next)
136
+                          then (funcall sub-fun accum next)
137
+                        finally (funcall sub-fun accum)))
138
+          (sequence
139
+           (loop with sub-fun = (funcall xf
140
+                                         (funcall builder rf))
141
+                 with sequence = input
142
+                 for (iterator limit from-end)
143
+                   = (multiple-value-list (sb-sequence:make-sequence-iterator sequence))
144
+                     then (list (sb-sequence:iterator-step sequence iterator from-end) limit from-end)
145
+                 for accum
146
+                   = (funcall sub-fun)
147
+                     then (funcall sub-fun accum next)
148
+                 until (sb-sequence:iterator-endp sequence iterator limit from-end)
149
+                 for next = (sb-sequence:iterator-element sequence iterator)
150
+                 finally (funcall sub-fun accum))))))))
151
+
152
+(defun tmp ()
153
+  (build 'build-array
154
+         (alexandria:compose (map '1+)
155
+                             (map (lambda (v)
156
+                                    (* v 3)))
157
+                             (filter 'oddp))
158
+         #(1 2 3 4)))