3 ms·
The lambdas are quoted because they’re lambdas, not because they’re arguments. In a more fully-featured Lisp, the code (LAMBDA () 42) would evaluate to a speci
by anderskaseorg 5y ago
The lambdas are quoted because they’re lambdas, not because they’re arguments.
In a more fully-featured Lisp, the code (LAMBDA () 42) would evaluate to a special function data type with no direct representation in terms of cons cells. That’s a luxury this implementation can’t afford, so its data representation of a function is the same as its code representation: a cons cell whose car is the atom LAMBDA. With this design, the code (LAMBDA () 42) would be self-evaluating: it would evaluate to the same thing as the code (QUOTE (LAMBDA () 42)). But since the latter syntax works, there’s no need to waste bytes supporting the former syntax.
In the absence of that special support, the default meaning of the code (LAMBDA () 42) is to call a function named LAMBDA, just like the code (FOO () 42) calls a function named FOO.
- akkartik 5y agoI see. Kinda. The 'lambda in the first line isn't quoted. And the eval itself seems identical to JMC. So this is just the substrate level and only when evaluating args? Does that seem right?
- jart 5y agoThe first item in the list is evaluated, just like the other items in the list. For example: ((LAMBDA (ARG1 ARG2 ARG3) <YOUR PROGRAM GOES HERE>) (QUOTE VAL1) (QUOTE VAL2) (QUOTE VAL3)) Is the same syntax as a normal function call: (PROGRAM (QUOTE VAL1) (QUOTE VAL2) (QUOTE VAL3)) The only difference is that, since it's the root-level program, the binding of the name PROGRAM can't have happened yet. So it's simply inlined into the expression. The Apply() part of the evaluator has a special case for recognizing this, which causes evaluation of the first argument to terminate: int Apply(int fn, int x, int a) { int t1, si, ax; if (ISATOM(fn)) { switch (fn) { case ATOM_CAR: return Car(Car(x)); case ATOM_CDR: return Cdr(Car(x)); case ATOM_ATOM: return ISATOM(Car(x)) ? ATOM_T : NIL; case ATOM_CONS: return Cons(Car(x), Car(Cdr(x))); case ATOM_EQ: return Car(x) == Car(Cdr(x)) ? ATOM_T : NIL; default: // recurse to turn (FUNC ARG1 ARG2) into ((LAMBDA ...) ARG1 ARG2) return Apply(Eval(fn, a), x, a); } } // evaluate ((LAMBDA ...) ARG1 ARG2) if (Car(fn) == ATOM_LAMBDA) { t1 = Cdr(fn); si = Pairlis(Car(t1), x, a); ax = Car(Cdr(t1)); return Eval(ax, si); } return UNDEFINED; } The Eval() function which calls Apply() has already evaluated all the list items except for the first one, which is left to Apply() to figure out. The reason why things are this way is because, in order to save space, the REPL loop doesn't maintain a persistent set of global variables. Due to the NIL here, each expression is its own hermetic job: void Repl(void) { for (;;) { Print(Eval(Read(), NIL)); } } If you changed that NIL to be an alist that's populated by things like defun / setq / etc. in the global scope, then the usability of the language improves dramatically. We didn't do it due to the singular focus on size. But at the same time that simply means we left fun opportunities for improvement to anyone wishing to hack on the codebase and make it their own.
- akkartik 5y agoOk that makes sense. Thanks!