by kazinator | 2025-04-14 19:19:28+00:00
I've been working on this for TXR Lisp. The next, release 300, will support infix.

The core expression in your example expression will parse as you have written it. Except we have to add ifx:

1> (ifx (if (0 <= x + m && x + m <= n) (prinl 'do-this))) ** expr-1:1: warning: unbound variable x ** expr-1:1: warning: unbound variable m ** expr-1:1: warning: unbound variable x ** expr-1:1: warning: unbound variable m ** expr-1:1: warning: unbound variable n ** expr-1:1: unbound variable x

Though it doesn't work since it's not a complete example with defined variables, we can quote it and expand it to see what the expansion looks like, which answers your question of how you write it one underlying Lisp:

1> (expand '(ifx (if (0 <= x + m && x + m <= n) (prinl 'do-this)))) (if (and (<= 0 (+ x m)) (<= (+ x m) n)) (prinl 'do-this))

The ifx macro deosn't have to be used for every expression. Inside ifx, infix expressions are detected at any nesting depth. You can put it around an entire file:

(ifx (defstruct user () id name) (defun add (x y) (x + y)) ...)

Autodetection of infix add some complexity and overhead to the code walking process that expands macros (not to mention that it's newly introduced) so we wouldn't want to have it globally enabled everywhere, all the time.

It's a compromise; infix syntax in the context of Lisp is just something we can support for a specific benefit in specific use case scenarios. It's mainly for our colleagues who find it a barrier not to be able to use infix.

In this particular infix implementation, a full infix expression not contained inside another infix expression is still parenthesized (because it is a compound form, represented as a list). You can see from the following large exmaple that it still looks like LIsp; it is a compromise.

I took an example FFT routine from the book Numerical Recipes in C and transliterated it, to get a feel for what it's like to write a realistic numerical routine that contains imperative programming "warts" like using assignments to initialize variables and such:

(defun fft (data nn isign) (ifx (let (n nmax m j istep i wtemp wpr wpi wr wi theta tempr tempi) (n := nn << 1) (j := 1) (for ((i 1)) ((i < n)) ((i += 2)) (when (j > i) (swap (data[j]) (data[i])) (swap (data[j + 1]) (data[i + 1]))) (m := nn) (while (m >= 2 && j > m) (j -= m) (m >>= 1)) (j += m)) (nmax := 2) (while (n > nmax) (istep := nmax << 1) (theta := isign * ((2 * %pi%) / nmax)) (wtemp := sin 0.5 * theta) (wpr := - 2.0 * wtemp * wtemp) (wpi := sin theta) (wr := 1.0) (wi := 0.0) (for ((m 1)) ((m < nmax)) ((m += 2)) (for ((i m)) ((i <= n)) ((i += istep)) (j := i + nmax) (tempr := wr * data[j] - wi * data[j + 1]) (tempi := wr * data[j + 1] + wi * data[j]) (data[j] := data[i] - tempr) (data[j + 1] := data[i + 1] - tempi) (data[i] += tempr) (data[i + 1] += tempi)) (wr := (wtemp := wr) * wpr - wi * wpi + wr) (wi := wi * wpr + wtemp * wpi + wi)) (nmax := istep)))))

It was very easy to transliterate the C code into the above, because of the infix. The remaining outer parentheses are trivial.

A smaller example is a quadratic roots calculation, from the test suite. This one has the ifx outside the defun:

(ifx (defun quadratic-roots (a b c) (let ((d (sqrt b * b - 4 * a * c))) (list ((- b + d) / 2 * a) ((- b - d) / 2 * a)))))

sqrt is a function, but treated as a prefix operator with a low precedence so parentheses are not required.