Saturday, September 19, 2009

Move, Down the road I go

Louis Reasoner doesn't care for all the work he had to do just to make it possible to read and clear the counter. He decides to write a macro to help. It's pretty simple:
(define-syntax register-counter!
  (syntax-rules ()
    ((register-counter! variable)
     (add-counter! (quote variable)
                   (lambda () variable) ;; reader function
                   (lambda () (set! variable 0)) ;; clear function
                   ;; maybe more functions here some day
                   ))))

(define lib-mapcar 
  (let ((calls-to-f 0))
    (register-counter! calls-to-f)
    (lambda (f list)
      (cond ((pair? list)
             (let ((h (car list)))
               (cons (begin
                      (set! calls-to-f (+ calls-to-f 1))
                      (f h))
                     (lib-mapcar f (cdr list)))))
            ((null? list) '())
            (else (error "improper list"))))))
This works nicely.

Management is insisting that the Lisp system support ‘constant ref arguments’. They are a bit unclear as to what the advantages are, but they say that they are absolutely necessary in order to assure investors that modern software techniques are supported. In disgust, Alyssa P. Hacker writes these macros and then takes a vacation:
(define-syntax ref
  (syntax-rules ()
    ((ref var) (lambda () var))))

(define-syntax deref
  (syntax-rules ()
    ((deref var) (var))))
Louis Reasoner takes a look at the code, thinks for a long while, then comes up with a test case:
(define (fib x)
  (if (< (deref x) 2)
      (deref x)
      (let ((x1 (- (deref x) 1))
            (x2 (- (deref x) 2)))
        (+ (fib (ref x1)) (fib (ref x2))))))

(let ((arg 7)) (fib (ref arg)))
=> 13
But Louis sees a problem: suppose we have a function that takes several ref arguments. It's such a pain to write something like this:
(foo (ref x) (ref y) (ref z) (ref a) b c)
So he writes his own macro:
(define-syntax lref
  (syntax-rules ()
    ((lref var ...)
     (list 
       (cons (quote var) (lambda () var)) ...))))
For those unfamiliar with the ... notation, the idea is that it causes pattern repetition. lref will be an n-ary macro. Each var that is passed in will be turned into an entry in an alist.

Now it is much easier to write code like this:
  (foo (lref x y z a) b c) 
With all the references passed in as an alist. Foo will have to be tweaked:
(define (foo ref-args b c)
  (let-syntax ((deref (syntax-rules () 
                        ((deref var) ((cdr (assq 'var ref-args)))))))
    ;; example body for foo
    (display (+ (deref x) (deref y) c))))

(let ((x 3)
      (y 2)
      (z 1))
  (foo (lref x y z) 2 5))
=> 10
The boilerplate syntax at the beginning of foo is a pain, but Louis is sure that some kind of macro can take care of that as well.

There is a looming problem here, which I hope is fairly obvious given the last couple of posts. Spoiler below....

I want to be clear on something, though. In any sort of example like this, where there is a problem that is not immediately obvious, you have to write a fair amount of code to get to the heart of the issue. You have the option of using real-world code, but there is often so much going on in the code it is hard to see the specifics. You have the alternative of writing a toy example (like this one), but then you get the objection “no one would ever write such bad code”, so your example, while potentially a problem, is so convoluted that it would never occur in practice.

So here's the big looming problem: the callee needs to know the names of the arguments that the caller assigned. Stupid macros, passing an alist, and so-called constant references are just there for a good story. The problem is that if we wish to change the name of something in foo, we have to locate every possible caller and change the name there as well. The register-counter! has the same problem: if you change the name of the counter variable, you have to find any code that examines the counters and be sure it isn't looking for the old name.

Just use grep? What if they wrote some code over at Larry Liverless Labs that uses the old name? A big advantage of lexical scoping is that interior names are local. You can change them, add them, or remove them without examining the entire code base. The macros that Louis wrote fail because they expose the internal names to code outside the function.

Tomorrow, the advantages of hiding the code...

No comments: