Browse code
Add an actual implementation of lenses
Ed Langley authored on 29/04/2019 04:11:24
Showing 1 changed files
Showing 1 changed files
... | ... |
@@ -1,3 +1,78 @@ |
1 |
+(defpackage :data-lens.lenses |
|
2 |
+ (:shadow :set) |
|
3 |
+ (:use :cl)) |
|
4 |
+(in-package :data-lens.lenses) |
|
5 |
+ |
|
6 |
+(defun make-alist-lens (key) |
|
7 |
+ (lambda (cb) |
|
8 |
+ (lambda (alist) |
|
9 |
+ (let ((old-value (serapeum:assocdr key alist))) |
|
10 |
+ (cons (cons key (funcall cb old-value)) |
|
11 |
+ alist))))) |
|
12 |
+ |
|
13 |
+(defun make-plist-lens (key) |
|
14 |
+ (lambda (cb) |
|
15 |
+ (lambda (plist) |
|
16 |
+ (let ((old-value (getf plist key))) |
|
17 |
+ (list* key (funcall cb old-value) |
|
18 |
+ plist))))) |
|
19 |
+ |
|
20 |
+(defun make-hash-table-lens (key) |
|
21 |
+ (lambda (cb) |
|
22 |
+ (lambda (old-hash) |
|
23 |
+ (let ((old-value (gethash key old-hash))) |
|
24 |
+ (fw.lu:prog1-bind (new-hash (alexandria:copy-hash-table old-hash)) |
|
25 |
+ (setf (gethash key new-hash) |
|
26 |
+ (funcall cb old-value))))))) |
|
27 |
+ |
|
28 |
+;; imagine a lens here that uses the MOP to immutably update a class... |
|
29 |
+ |
|
30 |
+(defun over (lens cb rec) |
|
31 |
+ "Given a lens, a callback and a record, apply the lens to the |
|
32 |
+record, transform it by the callback and return copy of the record, |
|
33 |
+updated to contain the result of the callback. This is the fundamental |
|
34 |
+operation on a lens and SET and VIEW are implemented in terms of it. |
|
35 |
+ |
|
36 |
+A lens is any function of the form (lambda (fun) (lambda (rec) ...)) |
|
37 |
+that obeys the lens laws (where == is some reasonable equality |
|
38 |
+operator): |
|
39 |
+ |
|
40 |
+ (== (view lens (set lens value rec)) |
|
41 |
+ value) |
|
42 |
+ |
|
43 |
+ (== (set lens (view lens rec) rec) |
|
44 |
+ rec) |
|
45 |
+ |
|
46 |
+ (== (set lens value2 (set lens value1 rec)) |
|
47 |
+ (set lens value2 rec)) |
|
48 |
+ |
|
49 |
+If these conditions are met, (over (data-lens:<>1 lens1 lens2) ...) is |
|
50 |
+equivalent to using lens2 to focus the part lens1 focuses: note that |
|
51 |
+composition is \"backwards\" from what one might expect: this is |
|
52 |
+because composition composes the wrapper lambdas and applies the |
|
53 |
+lambda that actually pulls a value out of a record later." |
|
54 |
+ |
|
55 |
+ (funcall (funcall lens cb) |
|
56 |
+ rec)) |
|
57 |
+ |
|
58 |
+(defun set (lens value rec) |
|
59 |
+ "Given a lens, a value and a rec, immutably update the rec to |
|
60 |
+contain the new value at the location focused by the lens." |
|
61 |
+ (over lens |
|
62 |
+ (lambda (_) |
|
63 |
+ (declare (ignore _)) |
|
64 |
+ value) |
|
65 |
+ rec)) |
|
66 |
+ |
|
67 |
+(defun view (lens rec) |
|
68 |
+ "Given a lens and a rec, return the focused value" |
|
69 |
+ (over lens |
|
70 |
+ (lambda (value) |
|
71 |
+ (return-from view |
|
72 |
+ value)) |
|
73 |
+ rec)) |
|
74 |
+ |
|
75 |
+ |
|
1 | 76 |
(defpackage :data-lens |
2 | 77 |
(:use :cl) |
3 | 78 |
(:import-from #:serapeum #:op #:defalias) |
... | ... |
@@ -10,6 +85,7 @@ |
10 | 85 |
#:defalias #:<> #:<>1)) |
11 | 86 |
(in-package :data-lens) |
12 | 87 |
|
88 |
+ |
|
13 | 89 |
(declaim |
14 | 90 |
(inline data-lens:over data-lens:transform-tail |
15 | 91 |
data-lens:applicable-when data-lens:of-min-length |
... | ... |
@@ -17,6 +93,7 @@ |
17 | 93 |
data-lens:compress-runs data-lens:combine-matching-lists |
18 | 94 |
data-lens:juxt data-lens:element data-lens:sorted)) |
19 | 95 |
|
96 |
+;;; TODO: consider making this wrap defalias? |
|
20 | 97 |
(defmacro shortcut (name function &body bound-args) |
21 | 98 |
`(eval-when (:load-toplevel :compile-toplevel :execute) |
22 | 99 |
(setf (fdefinition ',name) |