lisplexical-scopedynamic-scope

Should code with trampoline and Y combinator work in lisp with dynamic scope?


I have lisp in javascript which is similar to scheme. It can be used with lexical and dynamic scopes. I was not sure how dynamic scope works and it's seems ok but this code don't work when scope is dynamic:

(define Y
    (lambda (h)
       ((lambda (x) (x x))
           (lambda (g)
              (h (lambda args (apply (g g) args)))))))

(define (trampoline f)
    (lambda args
       (let ((result (apply f args)))
           (while (eq? (type result) "function")
               (set result (result)))
            result)))

(define (! n)
     ((trampoline (Y (lambda (f)
          (lambda (n acc)
             (if (== n 0)
                 acc
                 (lambda ()
                     (f (- n 1) (* n acc)))))))) n 1))

(print (! 1000))

it works fine when scope is lexical. Should this code work when scope is dynamic? Right now it just do nothing and I don't know why but wanted to be sure that this code should work before I start debugging and make my dynamic scope break because of this.

My lisp with demo is here https://jcubic.github.io/lips/ but the code that make this work for lexical scope is not yet published so it will not work. (it's in devel branch and I can create codepen demo with it or using Stack Snippet).


Solution

  • I don't see how trampoline can work with dynamic scoping.

    Simplified evaluation:

    (define Y ...)
    

    Now Y is bound (to some value).

    (define (trampoline f)
        (lambda args
           (let ((result (apply f args)))
               ...)))
    

    Now trampoline is bound to (lambda (f) (lambda args (let ((result (apply f args))) ...))).

    (define (! n)
         ((trampoline ...) n 1))
    

    Now ! is bound to (lambda (n) ((trampoline ...) n 1)).

    (print (! 1000))
    

    We evaluate the inner call first, so we need to resolve ! and apply it to 1000.

    By the definition of ! above we bind n to 1000 and evaluate ((trampoline ...) n 1).

    We need to call trampoline. By the definition of trampoline above, we bind f to ... and return (lambda args (let ((result (apply f args))) ...)).

    We return from trampoline and undo the binding of f.

    We now need to evaluate ((lambda args (let ((result (apply f args))) ...)) n 1) (applying the return value of trampoline to n and 1).

    n is currently bound to 1000, so this expression becomes ((lambda args (let ((result (apply f args))) ...)) 1000 1). To perform the call call we bind args to (1000 1).

    Now we need to evaluate (apply f args) (to bind the result to result as part of let). apply is in the standard library. args was just bound to (1000 1) above. But there is no binding for f.

    At this point we should throw an error: The only binding of f we've seen so far was during the call to trampoline (where f was a parameter). But that call has already returned and the binding was removed, so f is unbound.


    Live demo (using a Perl version of your code where all bindings are made dynamic manually): https://ideone.com/DWjwBj

    It blows up as predicted: Can't use an undefined value as a subroutine reference for the line local $result = $f->(@args); because $f is unbound.

    If you change all bindings to lexical (replace all occurrences of local by my), $fac->(5) returns 120 as expected.