That's a fun one. symbol-macros don't have an equivalent in lisp functions as far as I'm aware - `(symbol-lambda-let (x (lambda () (list foo x)) (list z z))` ~= `(list (foo z) (foo z))` isn't a thing.
Speculate that such a thing did exist. Call it symbol-lambda for less confusion with the symbol-function that does exist in common lisp. It's a function that takes no arguments, being a distinct premise from a function that takes the empty list. `(apply func some-list)` would be an error. But one wishes to call it implicitly when it shows up in lists. Related to and distinct from thunks.
Thus it's a function executed by eval, not by apply. Specifically if eval calls into eval-symbol when called on symbols, one way to go is perform the environment lookup etc as usual, and then afterwards check whether the thing one found in the environment is a symbol-lambda. Something like:
It doesn't take arguments, the same way symbol-macros don't take arguments.
The fexpr equivalent could be identical (since it's mostly distinguished from lambda by not evaluating arguments and there aren't any), or maybe the fexpr form gets passed the environment and the lambda form gets passed the empty environment instead on efficiency grounds.
I'm not totally convinced. Its straightforward to add to an fexpr-based evaluator. I think the proper model is that it's a function with a different calling convention to lambda and to fexpr. In the same way that eval-on-list tends to end up in a tail call to apply, eval-on-symbol can end up in a tail call to symbol-apply which usually returns the argument unchanged.
However passing one of these things directly to eval, as opposed to passing a symbol bound to one to eval, is also possible. So eval picks up a third case for eval-on-function, where fexpr and lambda get returned unchanged, but symbol-lambda/fexpr/name-tbd get evaluated.
Also the evaluation of the symbol-lambda might need to know what name it was invoked through. That usually isn't the case and interacts poorly with renaming variables. That also distinguishes passing it to eval as a function from calling it by writing down a symbol that resolves to one.
Overall this leaves me with a sense that I don't quite have the proper decomposition worked through yet.
Your thinking almost exactly parallels mine about this topic. I formed the same idea about lambdas that take only an environment being bound to symbols and whatnot.
I got to something resembling this part:
> With-slots then turns into something like:
That's when I hit the problem that "turns into" is macro code generation!
If the fexpr has to do code generation on the fly and eval it, it's doing a macro's job, badly, since the macro can do it upfront.
Speculate that such a thing did exist. Call it symbol-lambda for less confusion with the symbol-function that does exist in common lisp. It's a function that takes no arguments, being a distinct premise from a function that takes the empty list. `(apply func some-list)` would be an error. But one wishes to call it implicitly when it shows up in lists. Related to and distinct from thunks.
Thus it's a function executed by eval, not by apply. Specifically if eval calls into eval-symbol when called on symbols, one way to go is perform the environment lookup etc as usual, and then afterwards check whether the thing one found in the environment is a symbol-lambda. Something like:
It doesn't take arguments, the same way symbol-macros don't take arguments.The fexpr equivalent could be identical (since it's mostly distinguished from lambda by not evaluating arguments and there aren't any), or maybe the fexpr form gets passed the environment and the lambda form gets passed the empty environment instead on efficiency grounds.
With-slots then turns into something like:
I'm not totally convinced. Its straightforward to add to an fexpr-based evaluator. I think the proper model is that it's a function with a different calling convention to lambda and to fexpr. In the same way that eval-on-list tends to end up in a tail call to apply, eval-on-symbol can end up in a tail call to symbol-apply which usually returns the argument unchanged.However passing one of these things directly to eval, as opposed to passing a symbol bound to one to eval, is also possible. So eval picks up a third case for eval-on-function, where fexpr and lambda get returned unchanged, but symbol-lambda/fexpr/name-tbd get evaluated.
Also the evaluation of the symbol-lambda might need to know what name it was invoked through. That usually isn't the case and interacts poorly with renaming variables. That also distinguishes passing it to eval as a function from calling it by writing down a symbol that resolves to one.
Overall this leaves me with a sense that I don't quite have the proper decomposition worked through yet.