(defun quasiquote-y (f)
`(lambda (n)
(funcall (function ,f) (function ,f) n)))
Perhaps surprisingly, this does work in dynamically-scoped Lisp-2s like Elisp (which, unlike Lisp 1.5, doesn't have FUNARG), even for λ-expressions: (funcall
(quasiquote-y '(lambda (f n)
(if (< n 2) 1
(* n (funcall f f (- n 1))))))
4)
Defining a version of QUASIQUOTE-Y that works for the following subtly different LAMBDA form has so far escaped me but seems like it ought to be feasible: (lambda (f n)
(if (< n 2) 1
(* n (funcall f (- n 1)))))
Now, of course, in modern Lisps, we consider it a barbaric practice to treat a list beginning with the symbol LAMBDA as a function as such, as opposed to a piece of code which could be EVALed to produce a function. But this ancient abomination remains honored in the dark precincts of Elisp, and of course was entirely supported in Lisp 1.5 (though doing the equivalent of QUASIQUOTE would have been considerably more awkward; FUNARG was the favored design error of the day) and McCarthy 1959.ftp://publications.ai.mit.edu/ai-publications/pdf/AIM-199.pdf ("The function of FUNCTION") relates the 1970 perspective on this problem.
Then dynamic scoping interferes badly with tail call elimination (TCE), because bindings do not have indefinite extent and need to be removed at some point. The exact point of removal leads to all kinds of interesting problems. The short version is, you can have TCE or correct bindings, but not both. So a dynamically scoped Y will kind of "work", but eventually either cause a FUNARG problem or overflow the stack.
Moses points out in his paper that none of this is particularly difficult if you use deep binding ("deep access", as he calls it) rather than shallow binding ("shallow access"): you "just" "abandon a stack and use a structure which is quite close to an alist", but of course that means that now all your variable lookups take O(ASSQ) time, as he sort of points out in footnote 4 on p. 12.
It's interesting that (though he cites Landin!) the idea of lexical scoping seems not to have entered his mind; he seems to be confusing it with what he calls "evaluating functions against the binding environment", which is similar but not quite the same.
https://openlibra.com/en/book/the-funarg-problem-explained is Weizenbaum's "unpublished" 1968 memo "The FUNARG problem explained" that Moses was working from. On skimming the memo, I think Weizenbaum did understand things in the modern sense and in particular solved it in a way that permitted proper garbage collection — but in SLIP-OPL, not LISP. Weizenbaum says (p. 55):
> Our solution to these problems is mainly that of providing a tree structured symbol table in place of the more usual stack structured symbol table. That solution is, of course, not original with us. It shows up, for example, in some versions of LISP and in Landin's SECD machine.
And at the opening of his memo he claims that "the original LISP implementation solved that problem," which I take to mean the problem as he understood it; in particular, he critique's Bobrow's 1967 paper on FUNARG with shallow binding: "Still, years after that implementation, the problem remains ill understood. Bobrow, for example, published a faulty solution for it only recently."
Of course, the fact that Weizenbaum thinks the original LISP "solved that problem" doesn't strongly imply that it solved it correctly, much less with TCE.
He doesn't seem to mention TCE, unless I missed it. I think the modern understanding of the importance of TCE dates from Scheme and the whole "ΛTU" series of AI memos — at the time I think it was considered merely an optimization. But you're certainly correct that if you want to loop indefinitely with Y you need precise TCE.
Interestingly the Bobrow paper seems to have been published a year earlier as a BBN Scientific Report: https://apps.dtic.mil/dtic/tr/fulltext/u2/647601.pdf but I haven't compared this version to the ACM version. On p.15 he describes in a footnote the degree to which his approach, which definitely will not work for upwards funargs, does work in BBN LISP:
> *With appropriate passing down of pushdown list [call stack] pointers we can even achieve the same effect as the standard FUNARG device on the 7094 for functional arguments. This FUNARG device preserves local context well enough to handle the Knuth's [sic] "Man and Boy" compiler problem (Algol Bulletin 18.1).
I admit I'm certainly no expert on late-1960s LISP code; my acquaintance is mostly limited to what appears in popular AI memos and HOPL papers.
TCE was also a non-issue before the LTU papers, because it was (1) not necessary, you could just use a tag body, and (2) impossible to get right with dynamic scoping. Either you unbind after tail-calling and get no TCE, or you unbind before tail-calling and get a FUNARG problem.
If you want to experiment with LISP 1.5, there are 709 emulators out there, complete with original LISP tapes. E.g.: http://www.cozx.com/dpitts/ibm7090.html
Be prepared for some weirdness, though! Programs are entered as
CONS (FOO BAR)
meaning (CONS (QUOTE FOO) (QUOTE BAR)), there are no comments, and
in-situ lambda functions do not work. For instance, (LAMBDA (X) X) (FOO)
will evaluate, but DEFINE (( (TEST (LAMBDA (Q) ((LAMBDA (X) X) Q))) ))
TEST (FOO)
will not. It is fun to explore, though.