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

148 Views Asked by At

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).

2

There are 2 best solutions below

2
On BEST ANSWER

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.

0
On

No. Trampoline and Y combinators work with closures.

Dynamic scope has no closures so a procedure/function that refers to a free variable means whatever variable with that name in the call stack of the program.

In Lexical scope it is the variables captured when the lambda was created. Thus the code:

(define test 10)

(define (make-adder test)
  (lambda (v) (+ test v)))

(define add20 (make-adder 20))

(add20 5) 
; ==> 25 in lexical scope
; ==> 15 in dynamic scope 

The reson is simple. The function returned by the make-adder stores the value 20 as test, while in dynamic scope test is whatever is bound closest so it's the local variable 10. Also when calling:

(let ((test 30))
  (add20 5))
; ==> 25 in lexical scope
; ==> 35 in dynamic scope

Now Common Lisp has dynamic scope and lexical scope. A dynamically scoped variable is one that is defined top level with defvar, defparameter or declared special. This is so prone to errors that we have a special naming for such variables using *earmuffs*.

Scheme has parameters that are mutable objects and there are syntax for updating and restoring it so that it will act as a dynamic variable.

EDIT

I've tested your lexical and dynamic lisp and both seem to work as intended.