git.fiddlerwoaroof.com
Browse code

Fix implicit serapeum dependency, fix lenses to return functors

Ed Langley authored on 30/04/2019 06:07:10
Showing 2 changed files
... ...
@@ -4,7 +4,7 @@
4 4
   :license "MIT"
5 5
   :depends-on (:cl-ppcre
6 6
                :alexandria
7
-               )
7
+               :serapeum)
8 8
   :serial t
9 9
   :components ((:file "lens")))
10 10
 
... ...
@@ -4,6 +4,82 @@
4 4
   (:export :over :set :view :make-alist-lens :make-plist-lens :make-hash-table-lens))
5 5
 (in-package :data-lens.lenses)
6 6
 
7
+#+fw.dev
8
+(progn
9
+  ;; maybe functor implementation
10
+  (defclass maybe ()
11
+    ())
12
+  (defclass just (maybe)
13
+    ((%v :initarg :value :reader value)))
14
+  (defclass nothing (maybe)
15
+    ())
16
+
17
+  (defun just (value)
18
+    (make-instance 'just :value value))
19
+  (defun nothing (&optional value)
20
+    (declare (ignore value))
21
+    (make-instance 'nothing))
22
+
23
+  (defgeneric maybe (default value)
24
+    (:method (default (value just))
25
+      (value value))
26
+    (:method (default (value nothing))
27
+      default))
28
+
29
+  (defgeneric maybe-apply (function value)
30
+    (:method (function (value just))
31
+      (just (funcall function (value value))))
32
+    (:method (function (value nothing))
33
+      value))
34
+
35
+  (defmethod print-object ((o just) s)
36
+    (format s "#.(~s ~s)"
37
+            'just
38
+            (value o)))
39
+
40
+  (defmethod print-object ((o nothing) s)
41
+    (format s "#.(~s)"
42
+            'nothing)))
43
+
44
+;; identity functor, necessary for set and over
45
+(defclass identity- ()
46
+  ((%v :initarg :value :reader unidentity)))
47
+
48
+(defun wrap-identity (v)
49
+  (make-instance 'identity- :value v))
50
+
51
+(defmethod print-object ((o identity-) s)
52
+  (format s "#.(~s ~s)"
53
+          'wrap-identity
54
+          (unidentity o)))
55
+
56
+;; constant functor, necessary for view
57
+(defclass constant- ()
58
+  ((%v :initarg :value :reader unconstant)))
59
+
60
+(defun wrap-constant (v)
61
+  (make-instance 'constant- :value v))
62
+
63
+(defmethod print-object ((o constant-) s)
64
+  (format s "#.(~s ~s)"
65
+          'wrap-constant
66
+          (unconstant o)))
67
+
68
+(defgeneric fmap (function data)
69
+  (:method (function (data identity-))
70
+    (wrap-identity
71
+     (funcall function
72
+              (unidentity data))))
73
+  (:method (function (data constant-))
74
+    data)
75
+  (:method (function (data list))
76
+    (mapcar function data))
77
+  (:method (function (data vector))
78
+    (map 'vector function data))
79
+  #+fw.dev
80
+  (:method (function (data maybe))
81
+    (maybe-apply function data)))
82
+
7 83
 (defun over (lens cb rec)
8 84
   "Given a lens, a callback and a record, apply the lens to the
9 85
 record, transform it by the callback and return copy of the record,
... ...
@@ -23,53 +99,93 @@ operator):
23 99
    (== (set lens value2 (set lens value1 rec))
24 100
        (set lens value2 rec))
25 101
 
102
+The inner lambda returns a functor that determines the policy to be
103
+applied to the focused part.  By default, this only uses IDENTITY- and
104
+CONSTANT- in order to implement the lens operations over, set and
105
+view.
106
+
26 107
 If these conditions are met, (over (data-lens:<>1 lens1 lens2) ...) is
27 108
 equivalent to using lens2 to focus the part lens1 focuses: note that
28 109
 composition is \"backwards\" from what one might expect: this is
29 110
 because composition composes the wrapper lambdas and applies the
30 111
 lambda that actually pulls a value out of a record later."
112
+  (unidentity
113
+   (funcall (funcall lens (lambda (x) (wrap-identity (funcall cb x))))
114
+            rec)))
31 115
 
32
-  (funcall (funcall lens cb)
33
-           rec))
116
+(defun view (lens rec)
117
+  "Given a lens and a rec, return the focused value"
118
+  (unconstant
119
+   (funcall (funcall lens (lambda (x) (wrap-constant x)))
120
+            rec)))
34 121
 
35
-(defun set (lens value rec)
122
+(defun set (lens v rec)
36 123
   "Given a lens, a value and a rec, immutably update the rec to
37 124
 contain the new value at the location focused by the lens."
38
-  (over lens
39
-        (lambda (_)
40
-          (declare (ignore _))
41
-          value)
42
-        rec))
125
+  (unidentity
126
+   (funcall (funcall lens (lambda (_) _ (wrap-identity v)))
127
+            rec)))
43 128
 
44
-(defun view (lens rec)
45
-  "Given a lens and a rec, return the focused value"
46
-  (over lens
47
-        (lambda (value)
48
-          (return-from view
49
-            value))
50
-        rec))
129
+#+fw.dev
130
+(progn
131
+  ;; "fake" functors that don't assume a functor result to their
132
+  ;; callback
133
+  (defun over* (lens cb rec)
134
+    (funcall (funcall lens cb)
135
+             rec))
136
+
137
+  (defun set* (lens value rec)
138
+    (over lens
139
+          (lambda (_)
140
+            (declare (ignore _))
141
+            value)
142
+          rec))
143
+
144
+  (defun view* (lens rec)
145
+    (over lens
146
+          (lambda (value)
147
+            (return-from view*
148
+              value))
149
+          rec)))
150
+
151
+(defun make-alist-history-lens (key)
152
+  "A lens for updating a alist, preserving previous values"
153
+  (lambda (cb)
154
+    (lambda (alist)
155
+      (fmap (lambda (new)
156
+              (cons (cons key new)
157
+                    alist))
158
+            (funcall cb (serapeum:assocdr key alist))))))
51 159
 
52 160
 (defun make-alist-lens (key)
161
+  "A lens for updating a alist, discarding previous values"
53 162
   (lambda (cb)
54 163
     (lambda (alist)
55
-      (let ((old-value (serapeum:assocdr key alist)))
56
-        (cons (cons key (funcall cb old-value))
57
-              alist)))))
164
+      (fmap (lambda (new)
165
+              (remove-duplicates (cons (cons key new)
166
+                                       alist)
167
+                                 :key #'car
168
+                                 :from-end t))
169
+            (funcall cb (serapeum:assocdr key alist))))))
58 170
 
59 171
 (defun make-plist-lens (key)
172
+  "A lens for updating a plist, preserving previous values"
60 173
   (lambda (cb)
61 174
     (lambda (plist)
62
-      (let ((old-value (getf plist key)))
63
-        (list* key (funcall cb old-value)
64
-               plist)))))
175
+      (fmap (lambda (new)
176
+              (list* key new
177
+                     plist))
178
+            (funcall cb (getf plist key))))))
65 179
 
66 180
 (defun make-hash-table-lens (key)
181
+  "A lens for updating a hash-table, discarding previous values"
67 182
   (lambda (cb)
68 183
     (lambda (old-hash)
69
-      (let ((old-value (gethash key old-hash)))
70
-        (fw.lu:prog1-bind (new-hash (alexandria:copy-hash-table old-hash))
71
-          (setf (gethash key new-hash)
72
-                (funcall cb old-value)))))))
184
+      (fmap (lambda (new)
185
+              (fw.lu:prog1-bind (new-hash (alexandria:copy-hash-table old-hash))
186
+                (setf (gethash key new-hash)
187
+                      new)))
188
+            (funcall cb (gethash key old-hash))))))
73 189
 
74 190
 ;; imagine a lens here that uses the MOP to immutably update a class...
75 191
 (defgeneric clone (obj &rest new-initargs &key)
... ...
@@ -83,6 +199,7 @@ contain the new value at the location focused by the lens."
83 199
   (defmethod clone ((obj foo) &key)
84 200
     (make-instance 'foo :a (a obj)))
85 201
 
202
+  ;;; needs to be updated for functor-based lens
86 203
   (defun a-lens (cb)
87 204
     (lambda (foo)
88 205
       (fw.lu:prog1-bind (new (clone foo))