Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Our scoping is a little broken #22

Closed
skx opened this issue Oct 7, 2022 · 1 comment
Closed

Our scoping is a little broken #22

skx opened this issue Oct 7, 2022 · 1 comment
Labels
wontfix This will not be worked on

Comments

@skx
Copy link
Owner

skx commented Oct 7, 2022

This was spun off from the comment in #21.

There is a problem with scoping which can be revealed by this snippet:

(set! fib (fn* (n)
               (if (<= n 1)
                   n
                 (+ (fib (- n 1)) (fib (- n 2))))))

(let* (n 0)
  (while (<= n 10)
    (do
        (print (fib n))
        (set! n (+ n 1) true))))

Specifically the "set! n" should update the value of the "n" variable inside the let-scope, but doesn't unless the "true" parameter is added.

There are three scopes here:

  • The global scope, shared by everything before we execute code.
  • A new scope introduced by the (let*binding.
  • A third scope entered when a function body is executed.

There's something not quite right about the effects when the function/let scope overlap.

skx added a commit that referenced this issue Oct 9, 2022
This now allows the use of an arbitrary body, as can be seen
in the following test:

```
(set! p 4)

(while (<= p 10)
    (print "Pxx: %s" p)
    (set! p (+ p 1) true)
)
```

The new approach defines a local lambda, with a random name, and
calls that recursively when the conditional is true.

This closes #21, though we still need to use the three-argument
form of (set!) as our scoping/environment is confused - that is
tracked in #22.
@skx
Copy link
Owner Author

skx commented Oct 10, 2022

There is a simple change that lets this specific example work:

diff --git a/eval/eval.go b/eval/eval.go
index 58dc712..f9da709 100644
--- a/eval/eval.go
+++ b/eval/eval.go
@@ -1019,7 +1019,7 @@ func (ev *Eval) eval(exp primitive.Primitive, e *env.Environment, expandMacro bo
 
                                // Create a new environment/scope to set the
                                // parameter values within.
-                               e = env.NewEnvironment(proc.Env)
+                               e = env.NewEnvironment(e)
 
                                // For each of the arguments that have been supplied
                                for i, x := range args {

But that still results in the following code not behaving as expected:

$ cat fail.lisp 
;; Setup a global variable.
(set! a 3)

;; Inside a new environment - which has b==3 - try to modify the global variable
(let* (b 3)
  (set! a 33))

;; The variable is not updated
(print "A:%s" a)


;; Now use a global set
(let* (b 3)
  (set! a 33 true))

;; The variable has been updated
(print "A:%s" a)

Basically entering a new function-body, or a lambda, or a (let, or a (let* scope will always mean that "set!" applies to that specific scope.

I guess this means there's a question - when you set a variable in a scope, which exists in the parent, should we always forward to the parent? Or should we accept that the user needs to know about this issuue?

I can see that if you always forward variable updates to the parent then we might have other surprises. Consider this code:

(set! a "foo")

..
(let* (a "cake")
   (set! a "chocolate"))
..

In that case it might be that the user deliberately wanted a local variable with the same name, and the global shouldn't be updated. If that is the case then it seems like we might want to allow the user to make the decision - and that probably means we end up with somethign like our extra-argument-to-set! functionality.

So:

  • Make it universal? Only new variables in a scope are local
    • Essentially making global variables always global, without allowing them to be shadowed locally.
  • Or leave as-is, and let the user make the choice:
    • "(set! a b)" == local
    • "(set! a b true)" == global

I'm torn ..

@skx skx added the wontfix This will not be worked on label Apr 27, 2023
@skx skx closed this as completed Dec 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
wontfix This will not be worked on
Projects
None yet
Development

No branches or pull requests

1 participant