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
Showing 2 changed files
... | ... |
@@ -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)) |