Hacker Timesnew | past | comments | ask | show | jobs | submitlogin

TXR Lisp is completely strictly evaluated, like many other Lisp dialects. Function argument expressions are reduced to their values, in left to right order. Then the application of the resulting values to the function takes place.

Yet, with macros I have this:

  ./txr -p '(mlet ((x (lcons 1 x))) x)'
  (1 1 1 1 1 1 1 1 1 ... nonterminating sequence of 1's ...
This created a circular list!

The special mlet ("magic let" or "mutual let") construct has allowed the expression which initializes s, (lcons 1 x), to refer to x.

This works because both mlet and lcons are macros. The lcons macro (rather, the code generated by the macro!) returns a lazy cons cell, without immediately evaluating its arguments 1 and x. In the case of x, this is a damn good thing because x is not yet initialized! When the lazy cons is accessed (when the list object is printed), the evaluation of x takes place. By that time, x holds the lazy cons cell, and since the variable x is in scope of the argument x in (lcons 1 x), the lazy cons is able to force, setting its CDR field back to itself, creating not a lazy list, but a circular list.

With lcons, I we can make a fibonacci function quite similarly to how you might do it in Haskell:

   (defun fib2 (a b)
     (lcons a (fib2 b (+ a b))))
We call this as (fib 1 1) and it gives us a lazy list.

Here, we have a marriage between a lazy data structure and a macro. Without lazy conses, the lcons macro would have no target language to expand into. Without the lcons macro, lazy conses can't be used in the above convenient way; we would have to write fib2 in terms of the macro expansion:

  $ ./txr -p "(sys:expand '(defun fib2 (a b)
                             (lcons a (fib2 b (+ a b)))))"
(defun fib2 (a b) (make-lazy-cons (lambda (#:lcons-0001) (rplaca #:lcons-0001 a) (rplacd #:lcons-0001 (fib2 b (+ a b))))))

The circular list mlet, when fully expanded looks like this, by the way:

  $ ./txr -p "(sys:expand '(mlet ((x (lcons 1 x))) x))"
  (let (#:g0001) (sys:setq #:g0001 (cons 'sys:promise
  (cons (lambda () (make-lazy-cons (lambda (#:lcons-0046)
  (rplaca #:lcons-0046 1)
  (rplacd #:lcons-0046 (force #:g0001))))) '(delay (lcons 1 x))))) (force #:g0001))
It's a gritty oat-meal of delays, lambdas, forces, and cons manipulation. One thing that is conspicuously absent amid the toenail clippings: what happened to the x variable? Haha!


Consider applying for YC's Summer 2026 batch! Applications are open till May 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: