Modf is a setf
like macro for functional programming. Like setf
, Modf knows
how to modify many Lisp objects such that a place will evaluate to a new value
of your choosing. Unlike setf
, Modf doesn’t mutate the data in anyway.
Instead it creates a new object with the requested changes in place. See my
blog post on Modf for a bit more info. Be warned though, while the gist is
definitely there, it has become a bit out of date as Modf has progressed.
To give a pathologic example, Modf allows you to change t
to nil
in the
following expression easily.
(defparameter *crazy* '(1 #(a (2 #(b (3 #(c (4 t))))))))
==> *CRAZY*
;; Change that last value with Modf
(modf
(cadr (aref (cadr (aref (cadr (aref (cadr *crazy*) 1)) 1)) 1))
nil)
==> (1 #(A (2 #(B (3 #(C (4 NIL)))))))
;; But note that the original data is unchanged
*crazy*
==> (1 #(A (2 #(B (3 #(C (4 T)))))))
;; Note that the Modf form looks a lot like how you would use setf
(setf
(cadr (aref (cadr (aref (cadr (aref (cadr *crazy*) 1)) 1)) 1))
nil)
==> NIL
*crazy*
==> (1 #(A (2 #(B (3 #(C (4 NIL)))))))
;; What does Modf gain us, take a look. Look it over. It's all necessary
(macroexpand
'(modf
(cadr (aref (cadr (aref (cadr (aref (cadr *crazy*) 1)) 1)) 1))
nil))
==>
(LET ((#:G1556 *CRAZY*))
(CONS (CAR #:G1556)
(LET ((#:G1555 (CDR #:G1556)))
(CONS
(LET ((#:G1554 (CAR #:G1555)))
(FUNCALL (MODF-FN AREF)
(LET ((#:G1553 (AREF #:G1554 1)))
(CONS (CAR #:G1553)
(LET ((#:G1552 (CDR #:G1553)))
(CONS
(LET ((#:G1551 (CAR #:G1552)))
(FUNCALL (MODF-FN AREF)
(LET ((#:G1550 (AREF #:G1551 1)))
(CONS (CAR #:G1550)
(LET ((#:G1549
(CDR #:G1550)))
(CONS
(LET ((#:G1548
(CAR #:G1549)))
(FUNCALL
(MODF-FN AREF)
(LET ((#:G1547
(AREF #:G1548 1)))
(CONS (CAR #:G1547)
(LET ((#:G1546
(CDR
#:G1547)))
(CONS NIL
(CDR
#:G1546)))))
#:G1548 1))
(CDR #:G1549)))))
#:G1551 1))
(CDR #:G1552)))))
#:G1554 1))
(CDR #:G1555)))))
The aim is to make this work for any Lisp object, including CLOS objects and structures.
It works similar to the way setf
works. With setf
expansions are defined
that take some kind of accessor function and turn it into a function that sets
the value at that place. In Modf, expansions are defined that turn an accessor
function into a function that builds a new object the specified change in place.
Modf strives to be portable and work everywhere as long as Modf expansion functions are defined. See the section Defining your own Modf Expansions. I aim to cover all primitive data types in CL. As of now, it should work in any Lisp when working with lists, arrays, hash tables, and, given you have defined your Modf expanders for your data types, class instances and structs.
For usability purposes, however, a lot of effort has gone into making Modf work
for data even if Modf expansions haven’t been defined. This is a very difficult
to impossible task. For one, we have no way of knowing which argument holds the
actual data structure. If no expansion is defined, we assume it is at the first
argument of the place (adjusting for apply
statements).
In the case of class accessor methods without defined Modf expansions, Closer-Mop is used to examine the data at run time and produce the functional changes. For structures we don’t even have Closer-Mop to help us and a series of heuristics are used at run time to try to invert accessor functions (again, only if no Modf expander has been defined).
If Modf doesn’t work for something common out of the box, feel free to post a bug report. It would be even better if you can think of a way figure out what to do.
Modf is tested regularly on the major Libre Software implementations (SBCL, CMUCL, CCL, CLISP, ECL).
Modf works my defining various expansion constructs. These expansion constructs can be in the form of:
- Rewrite Rules via
define-modf-rewrite
, a simple macro like facility that rewrites the place you want tomodf
into something simpler. Think(cadr x)
->(car (cdr x))
. - Expanders via
define-modf-expander
, a more general expansion mechanism where you define a function that is given the place to be modded, the current value of that place, and the new value it should be modded to. - Modf functions and Methods via
define-modf-function
anddefine-modf-method
, which you should think of as a way to define(setf fn)
like functions for Modf.
In principle, the last construct alone is enough to do anything you want. The others are included for your convenience and with thoughts of compiler optimization on the in place expansions performed by expanders.
For expanders and Modf functions/methods, you need to specify which argument actually holds the data that is being modified. This is given as an extra argument after the name.
- Tons of stuff with ABCL. It actually works for most things in ABCL, but I don’t have the patience to get the test suite running.
- Perhaps issues with order of evaluation. I haven’t gone through a thorough audit of this yet.
Working on it. This is supposed to be a literate program, but the comments in the source are chicken scratch.
Zach Kost-Smith <zachkostsmith@gmail.com>
3-Clause BSD. See file COPYING.