functional-programmingschememit-schemecdr

Using lambda with cdr and car in Scheme


I define a list by using quotes. Then, I try to define a lambda operation by using elements of this list which I get with cdr and car. But the defined lambda operation gives an error about its number of parameters. The error message is:

;The procedure #[compound-procedure XX] has been called with 1 argument; it requires exactly 2 arguments.

(define x '(lambda (n) (+ n 1)))
(cadr x) ;mit scheme interpreter displays (n)
(caddr x) ; this results in (+ n 1)
((lambda (cadr x)(caddr e)) 2) ; this is the problematic part which results in an error.

Solution: Creating a temporary environment and binding the formal parameters and actual parameters of lambda expressions in that environment and interpreting the body of the lambda expression with that environment.


Solution

  • When you do:

    (define x '(lambda (n) (+ n 1)))
    

    You are making the binding x to point at the list structure (lambda (n) (+ n 1)). It has nothing to do with lambda forms like:

    (define x2 (lambda (n) (+ n 1)))
    

    Where you can apply (x2 1) ; ==> 2 since it's value is a closure / procedure / function since the lambda form is evaluated.

    (lambda (cadr x)(caddr e)) doesn't evaluate (cadr x) rather it creates a closure with the formal parameter cadr and x such that you can apply the result ((lambda (cadr x) ...) 1 2) such that evaluating cadr in the closure becomes 1 and x becomes 2. The evaluation of (caddr e) happens when you apply thus if you call ((lambda (cadr x)(caddr e)) 'ignored1 'ignored2) it will return the same as evaluating (caddr e) in the environment the closure was created. It would be impossible to get (eval `(lambda ,(cadr x) ,(caddr e))) working to as you will have no way to handle free variables since you're mixing your host with your guest.

    Since you are making an interpreter your user defined procedures would be data structures and your apply will know what do to with it. The evaluation of the form should return something that can be identified as a closure and you cannot do any other code in your interpreter to fool it, has a reference to the lexical scope of the place it was evaluated and every part of the cdr if the lambda.

    One of mine does this:

    (define closure-tag (list 'closure)) ; make something that is not `eq?` with enything else
    
    (define (closure? expr)
      (and (pair? expr)
           (eq? closure-tag (car expr))))
    
    (define (lambda->closure expr env)
      `(,closure-tag ,env ,@(cdr expr)))
    

    So evaluating a lambda (lambda (n) (+ n 1)) becomes ((closure) ((#t . #t) ...) (n) (+ n 1)) and apply for ((lambda (n) (+ n 1)) 2) will evaluate (+ n 1) with the environment ((n . 2) (#t . #t) ...). The choice of structure is not relevant since the structure is an agreement between the evaluation of the lambda form and your apply.

    You could make lambda forms become procedures but it is still not a host version of the guest source, but rather some sort of optimization. One of my latest eval did this and took always 2 arguments. The argument list unevaluated and the environment. In eval lingo the primitives were curried with evlis and apply. Most design choices you do will have advantages as well as disadvantages and it's interesting to play with.