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.

What does Modf do?

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
   (cadr (aref (cadr (aref (cadr (aref (cadr *crazy*) 1)) 1)) 1))
  ==> (1 #(A (2 #(B (3 #(C (4 NIL)))))))

  ;; But note that the original data is unchanged
  ==> (1 #(A (2 #(B (3 #(C (4 T)))))))

  ;; Note that the Modf form looks a lot like how you would use setf
   (cadr (aref (cadr (aref (cadr (aref (cadr *crazy*) 1)) 1)) 1))
  ==> NIL

  ==> (1 #(A (2 #(B (3 #(C (4 NIL)))))))

  ;; What does Modf gain us, take a look.  Look it over.  It's all necessary
     (cadr (aref (cadr (aref (cadr (aref (cadr *crazy*) 1)) 1)) 1))

  (LET ((#:G1556 *CRAZY*))
    (CONS (CAR #:G1556)
          (LET ((#:G1555 (CDR #:G1556)))
             (LET ((#:G1554 (CAR #:G1555)))
               (FUNCALL (MODF-FN AREF)
                        (LET ((#:G1553 (AREF #:G1554 1)))
                          (CONS (CAR #:G1553)
                                (LET ((#:G1552 (CDR #:G1553)))
                                   (LET ((#:G1551 (CAR #:G1552)))
                                     (FUNCALL (MODF-FN AREF)
                                              (LET ((#:G1550 (AREF #:G1551 1)))
                                                (CONS (CAR #:G1550)
                                                      (LET ((#:G1549
                                                              (CDR #:G1550)))
                                                         (LET ((#:G1548
                                                                 (CAR #:G1549)))
                                                            (MODF-FN AREF)
                                                            (LET ((#:G1547
                                                                    (AREF #:G1548 1)))
                                                              (CONS (CAR #:G1547)
                                                                    (LET ((#:G1546
                                                                      (CONS NIL
                                                            #: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.

How does Modf work?

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.

Where does Modf work?

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).

Defining your own Modf expansions

Modf works my defining various expansion constructs. These expansion constructs can be in the form of:

  1. Rewrite Rules via define-modf-rewrite, a simple macro like facility that rewrites the place you want to modf into something simpler. Think (cadr x) -> (car (cdr x)).

  2. 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.

  3. Modf functions and Methods via define-modf-function and define-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.

Known issues

  1. 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.

  2. 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 <>


3-Clause BSD

Zach Kost-Smith <>
3 Clause BSD (