Perpetual Weekend

2 December 2006

A Better Let

Filed under: Uncategorized — shaurz @ 19:42

The “let” form is, in my humble opinion, the worst bit of syntax in Lisp, requiring lots of superfluous parenthesis. I have devised a new syntax which removes these redundancies and improves readability.

<let> = ( let [<name> <exp>]* <exp>+ )

For example, the below expression:

(let ((x (* a a))
      (y (+ a a)))
  (- x y))

Can be expressed as:

(let x (* a a)
     y (+ a a)
  (- x y))

Which is much more pleasant. It is also quite easy to parse - here’s my implementation, which converts the bindings into an immediate lambda application:

(define (expand-let x vars vals)
  (if (or (pair? (car x)) (nil? (cdr x)))
      `((lambda ,(reverse vars) ,@x) ,@(reverse vals))
      (expand-let (cddr x)
                  (cons (car x) vars)
                  (cons (cadr x) vals))))

(define-macro (let . x)
  (expand-let x '() '()))

Another approach, which I am quite fond of, is extending the basic syntax of lambda so that we can introduce new bindings using an infix equality. The bindings can appear anywhere in the body except the very end since it must evaluate to a value.

<lambda> = ( lambda <formals> <stmt>* <exp> )
<stmt>   = <name> = <exp>
         | <exp>

The syntax is quite cute and convenient. It is mostly unambigous since there is no valid reason to evaluate a variable followed by an “=” in a lambda body. For example:

(lambda (a)
  x = (* a a)
  y = (+ x x)
  (- x y))

When we introduce a binding in the lambda body, it is as if the rest of the body is wrapped in another lambda taking the new binding as an argument. The above expression is equivalent to:

(lambda (a)
  ((lambda (x)
    ((lambda (y) (- x y))
     (+ x x)))
   (* a a)))

It would make sense to allow the binding syntax in other block forms such as begin. This is pretty trivial:

(define-macro (begin . exps)
  `((lambda () ,@exps)))

At the moment I am reading many papers (Citeseer and ACM Portal can be addictive!) on functional language implementation. My original interpreter which I wrote before the one I released here used an internal pre-compiled AST with parent-linked array activation frames. My current rewritten version uses a much less efficient direct interpretation of the list structure with association list environments. This was mainly done as a fun exercise - now I want to move onto something better.

Programs will again be represented by an AST. Variable nodes were previously represented as a (level, offset) pair where the level was the number of parent links you had to follow up from the current environment. I will now have three types of variable: locally bound (simply a stack offset), free (an offset into a free variable frame - see below), or global.

Closures will be represented by a code pointer and an array of free variables. Bound variables will be passed on the stack which we can deterministically push and pop, so we don’t need to garbage-collect normal activation frames, only free variable frames. If you don’t used closed variables you don’t have to pay the price. Mutable closed variables will in general require a ref cell.

A nice optimisation, if we create multiple lambdas which share the same free variables, would be to share a single free variable frame. The compiler could determine which implementation will use less space. In some cases this will eliminate the need for ref cells. This will make object-oriented techniques built on lambdas acceptably efficient.

I am also going to change my object representation so I can have unboxed tagged types such as fixed-sized integers, chars, booleans and nil.

1 December 2006

Quasiquote redux

Filed under: Uncategorized — shaurz @ 0:02

I have updated my quasiquote macro to support unquote-splicing.

(define (expand-quasiquote x)
  (if (pair? x)
      (if (is? (car x) 'unquote)
          (cadr x)
      (if (is? (car x) 'unquote-splicing)
          (error "unquote-splicing not inside list")
      (if (and (pair? (car x)) (is? (caar x) 'unquote-splicing))
          (list 'append
                (cadar x)
                (expand-quasiquote (cdr x)))
          (list 'cons
                (expand-quasiquote (car x))
                (expand-quasiquote (cdr x))))))
      (list 'quote x)))

(define-macro (quasiquote x) (expand-quasiquote x))

(define-macro (unquote x)
  (error "unquote not inside quasiquote"))

(define-macro (unquote-splicing x)
  (error "unquote-splicing not inside quasiquote"))

On another note, I have discovered a wonderful beer called “Innis & Gunn”. It is aged in oak whiskey barrells and has a whiskey-like taste, yet it is quite light. Not a beer to gulp down though, sip slowly to really enjoy it. I’m beginning to sound like an advert!

It occurred to me that macro-expansion has at least two possible semantics: expand the arguments of a macro first, or not. I have currently implemented the latter since it seems more natural to me for a macro to be passed the expressions as-typed, but I am not sure if this is “correct”. I must investigate.

Blog at WordPress.com.