mirror of
https://github.com/fiddlerwoaroof/data-lens.git
synced 2025-11-08 18:26:32 +00:00
Add an actual implementation of lenses
This commit is contained in:
77
lens.lisp
77
lens.lisp
@ -1,3 +1,78 @@
|
|||||||
|
(defpackage :data-lens.lenses
|
||||||
|
(:shadow :set)
|
||||||
|
(:use :cl))
|
||||||
|
(in-package :data-lens.lenses)
|
||||||
|
|
||||||
|
(defun make-alist-lens (key)
|
||||||
|
(lambda (cb)
|
||||||
|
(lambda (alist)
|
||||||
|
(let ((old-value (serapeum:assocdr key alist)))
|
||||||
|
(cons (cons key (funcall cb old-value))
|
||||||
|
alist)))))
|
||||||
|
|
||||||
|
(defun make-plist-lens (key)
|
||||||
|
(lambda (cb)
|
||||||
|
(lambda (plist)
|
||||||
|
(let ((old-value (getf plist key)))
|
||||||
|
(list* key (funcall cb old-value)
|
||||||
|
plist)))))
|
||||||
|
|
||||||
|
(defun make-hash-table-lens (key)
|
||||||
|
(lambda (cb)
|
||||||
|
(lambda (old-hash)
|
||||||
|
(let ((old-value (gethash key old-hash)))
|
||||||
|
(fw.lu:prog1-bind (new-hash (alexandria:copy-hash-table old-hash))
|
||||||
|
(setf (gethash key new-hash)
|
||||||
|
(funcall cb old-value)))))))
|
||||||
|
|
||||||
|
;; imagine a lens here that uses the MOP to immutably update a class...
|
||||||
|
|
||||||
|
(defun over (lens cb rec)
|
||||||
|
"Given a lens, a callback and a record, apply the lens to the
|
||||||
|
record, transform it by the callback and return copy of the record,
|
||||||
|
updated to contain the result of the callback. This is the fundamental
|
||||||
|
operation on a lens and SET and VIEW are implemented in terms of it.
|
||||||
|
|
||||||
|
A lens is any function of the form (lambda (fun) (lambda (rec) ...))
|
||||||
|
that obeys the lens laws (where == is some reasonable equality
|
||||||
|
operator):
|
||||||
|
|
||||||
|
(== (view lens (set lens value rec))
|
||||||
|
value)
|
||||||
|
|
||||||
|
(== (set lens (view lens rec) rec)
|
||||||
|
rec)
|
||||||
|
|
||||||
|
(== (set lens value2 (set lens value1 rec))
|
||||||
|
(set lens value2 rec))
|
||||||
|
|
||||||
|
If these conditions are met, (over (data-lens:<>1 lens1 lens2) ...) is
|
||||||
|
equivalent to using lens2 to focus the part lens1 focuses: note that
|
||||||
|
composition is \"backwards\" from what one might expect: this is
|
||||||
|
because composition composes the wrapper lambdas and applies the
|
||||||
|
lambda that actually pulls a value out of a record later."
|
||||||
|
|
||||||
|
(funcall (funcall lens cb)
|
||||||
|
rec))
|
||||||
|
|
||||||
|
(defun set (lens value rec)
|
||||||
|
"Given a lens, a value and a rec, immutably update the rec to
|
||||||
|
contain the new value at the location focused by the lens."
|
||||||
|
(over lens
|
||||||
|
(lambda (_)
|
||||||
|
(declare (ignore _))
|
||||||
|
value)
|
||||||
|
rec))
|
||||||
|
|
||||||
|
(defun view (lens rec)
|
||||||
|
"Given a lens and a rec, return the focused value"
|
||||||
|
(over lens
|
||||||
|
(lambda (value)
|
||||||
|
(return-from view
|
||||||
|
value))
|
||||||
|
rec))
|
||||||
|
|
||||||
|
|
||||||
(defpackage :data-lens
|
(defpackage :data-lens
|
||||||
(:use :cl)
|
(:use :cl)
|
||||||
(:import-from #:serapeum #:op #:defalias)
|
(:import-from #:serapeum #:op #:defalias)
|
||||||
@ -10,6 +85,7 @@
|
|||||||
#:defalias #:<> #:<>1))
|
#:defalias #:<> #:<>1))
|
||||||
(in-package :data-lens)
|
(in-package :data-lens)
|
||||||
|
|
||||||
|
|
||||||
(declaim
|
(declaim
|
||||||
(inline data-lens:over data-lens:transform-tail
|
(inline data-lens:over data-lens:transform-tail
|
||||||
data-lens:applicable-when data-lens:of-min-length
|
data-lens:applicable-when data-lens:of-min-length
|
||||||
@ -17,6 +93,7 @@
|
|||||||
data-lens:compress-runs data-lens:combine-matching-lists
|
data-lens:compress-runs data-lens:combine-matching-lists
|
||||||
data-lens:juxt data-lens:element data-lens:sorted))
|
data-lens:juxt data-lens:element data-lens:sorted))
|
||||||
|
|
||||||
|
;;; TODO: consider making this wrap defalias?
|
||||||
(defmacro shortcut (name function &body bound-args)
|
(defmacro shortcut (name function &body bound-args)
|
||||||
`(eval-when (:load-toplevel :compile-toplevel :execute)
|
`(eval-when (:load-toplevel :compile-toplevel :execute)
|
||||||
(setf (fdefinition ',name)
|
(setf (fdefinition ',name)
|
||||||
|
|||||||
Reference in New Issue
Block a user