There exists a rich theory about duality in computation, a forgotten
twin of lambda calculus:
sequent calculus
https://ps.cs.uni-tuebingen.de/publications/ostermann22intro...
I recommend you check it out if you are at least a little curious
about duality in programming.
I've been thinking about duality at the core of programming language
design for a while now. Motivated by asynchronous computation and
mainly focused on the question: How to build programming abstractions
from input-output-duality? It is fascinating seeing the common
abstractions just naturally evolve from there.
As a preliminary result I wrote a theory of computation:
http://perma-curious.eu/e3lli/core/
It describes how to go from input-output-duality to an advanced lisp dialect.
That author has a bit of a misunderstanding about Lisp:
"Lisp evaluation works by mutual recursion of eval and apply. Eval looks at an expression and if it is a function application it calls apply. Apply in turn calls eval on the arguments and invokes its function on them. This is call-by-value."
In classical Lisp and its descendants, apply is a function. Indeed, the paradigm is call by value, and therefore that function receives all of its arguments already evaluated.
When eval determines that its input form is a function call, it recurses first on eval to evaluate the argument expressions to a list of values. It then uses apply, which is a function which takes two arguments: the function to be applied, and a list object of values to be the arguments. apply doesn't do any evaluating, just the binding of the function to the arguments. eval needs this API, without which it has no way to pass a dynamically constructed argument list to a function.
It's not clear if there is a meaningful duality there; apply is a service required by eval. eval is not required by apply. apply can be written in terms of eval, but that eval cannot then use that apply. Also, it's mildly ugly, because apply has to carefully add quotes to the argument material to prevent eval from evaluating it again, so that just the desired effect is obtained of a function application and no other evaluations.
I tried digging through both the article, SICP and your comment, and here are my thoughts: I think both statements are correct in their context.
The perma-curious.eu article is right about the eval-apply cycle as presented in SICP. The SICP metacircular evaluator presents eval and apply as two mutually recursive procedures that form the core of a Lisp interpreter. In this implementation, apply does evaluate arguments indirectly through eval. The mutual recursion between eval and apply is indeed fundamental to Lisp, and it's clearly explained in the "wizard book". The call-by-value evaluation strategy is also correctly described - arguments are evaluated before being passed to functions. This eval-apply cycle forms the essence of Lisp's execution model.
But your comment is also valid, your correction is more precise:
- apply is indeed a function that takes already-evaluated arguments
- eval does the evaluation of arguments before calling apply
- apply simply binds and executes the function with its arguments
- The relationship isn't truly dual - apply depends on eval, but not vice versa
I think the confusion came from conflating the built-in apply function with the apply procedure used in implementing an interpreter.
SICP is not the horse's mouth in this matter, and doesn't purport to be. It's presentations are designed to suit its pedagogical aims, not to build up a historically accurate Lisp.
Last I heard, they supposedly rendered the book and course into Python.
I've been thinking about duality at the core of programming language design for a while now. Motivated by asynchronous computation and mainly focused on the question: How to build programming abstractions from input-output-duality? It is fascinating seeing the common abstractions just naturally evolve from there.
As a preliminary result I wrote a theory of computation: http://perma-curious.eu/e3lli/core/ It describes how to go from input-output-duality to an advanced lisp dialect.