modf

2020-09-25

A SETF like macro for functional programming

Upstream URL

github.com/smithzvk/modf

Author

Zach Kost-Smith <zachkostsmith@gmail.com>

License

3 Clause BSD (http://opensource.org/licenses/BSD-3-Clause)
README

1Modf

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.

1.1What 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
  (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.

1.2How 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.

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

1.4Defining 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 thatrewrites the place you want to modf into something simpler. Think =(cadrx)= -> (car (cdr x)).
  2. Expanders via define-modf-expander, a more general expansion mechanismwhere you define a function that is given the place to be modded, thecurrent value of that place, and the new value it should be modded to.
  3. Modf functions and Methods via define-modf-function anddefine-modf-method, which you should think of as a way to define =(setffn)= 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.

1.5Known issues

  1. Tons of stuff with ABCL. It actually works for most things in ABCL, but Idon't have the patience to get the test suite running.
  2. Perhaps issues with order of evaluation. I haven't gone through a thoroughaudit of this yet.

1.6Documentation

Working on it. This is supposed to be a literate program, but the comments in the source are chicken scratch.

1.7Author

Zach Kost-Smith <zachkostsmith@gmail.com>

1.8License

3-Clause BSD. See file COPYING.

Dependencies (4)

  • alexandria
  • closer-mop
  • iterate
  • stefil

Dependents (1)

  • GitHub
  • Quicklisp