Extending uLisp's built-in operators


#1

Here’s a feature of uLisp that may not have occurred to everyone: you can redefine the built-in operators.

For example, suppose you want to extend ‘+’ to handle rational numbers, represented as a list of two integers. So, for example, 1/3 would be represented as:

(1 3)

First we make $+ a synonym for the built-in + operator:

(defvar $+ +)

Here’s the definition of rational addition:

(defun rat+ (a b)
  (list ($+ (* (first a) (second b)) (* (first b) (second a))) (* (second a) (second b))))

Note we take care to use $+ for normal addition, to avoid circularity in the next step.

Now we redefine + to check for rational number arguments:

(defun + (a b)
  (cond
   ((and (atom a) (atom b)) ($+ a b))
   (t (rat+ a b)))) 

After this we can do normal addition:

> (+ 2 3)
5

and rational addition:

> (+ '(1 3) '(1 5))
(8 15)

So 1/3 + 1/5 = 8/15.

To restore the built-in definition of + do:

(makunbound '+)

#2

In a similar vein, this can also help with debugging by redefining a function to, for example, print its arguments before calling the actual function. Here is a contrived example of a broken factorial function:

(defun fact (x)
  (let ((f 1))
    (dotimes (i x)
      (setq f (* i f)))
    f))

fact(3) should return 6, but returns 0:

22101> (fact 3)
0

Here is one way to redefine * to print its arguments and then call the original *:

(let*
    (($* *)
     (* (lambda (x y)
	      (format t "(* ~a ~a)~%" x y)
	      ($* x y))))
  (fact 3))

Running this gives:

22101> (let*     (($* *)      (* (lambda (x y) 	  (format t "(* ~a ~a)~%" x y) 	  ($* x y))))   (fact 3))
(* 0 1)
(* 1 0)
(* 2 0)
0

The first multiplication is multiplying by 0, not 1, causing the running product to become 0. (I found it quite satisfying to fix the bug and run the test again.)

Using let* makes the function binding temporary and ensures the debugging code doesn’t get mixed in with the real code.

While not necessary, note that the format call above is generating valid Lisp - this makes it easy to copy/paste/run a particular call afterwards to see what it does. This can be useful for graphics programs that don’t generate the right output.

Another possibility is to have the lambda form compute the result of the function call and put that in the format call:

(let*
    (($* *)
     (* (lambda (x y)
	      (let ((v ($* x y)))
	        (format t "(* ~a ~a) -> ~a~%" x y v)
	        v))))
  (fact 3))

Note that the locally bound names (here: $*, x, y, v) should not be in the global namespace or chaos is likely to ensue (I’ve been caught out by this in a related situation).

It occurs to me while writing this that it may be possible to automate the generation of such instrumentation in uLisp itself for user-defined functions by walking the lambda argument list. Perhaps scanning the documentation string would work for builtin functions; I’m not sure about macros and special forms.


#3

Nice idea, although you can achieve something similar with trace:

Debugging in uLisp