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

BUG? blocks can partially defer execution #39

Open
mantacid opened this issue Oct 3, 2024 · 3 comments
Open

BUG? blocks can partially defer execution #39

mantacid opened this issue Oct 3, 2024 · 3 comments

Comments

@mantacid
Copy link

mantacid commented Oct 3, 2024

I'm trying to make a lisp-like let operator, I discovered unexpected behavior in how blocks are executed.

## the let method will allow for lexical scope.
lexical = new
lexical.let = {bindings, body |
  0.to (bindings.keys.length - 1) {i|
    lexical.add_method "#{bindings.keys[i]}" {|bindings[bindings.keys[i]]}
  }
  lexical.with_this {invoke -> body}
  0.to (bindings.keys.length - 1) {i|
    lexical.del_method "#{bindings.keys[i]}"
  }
}

## test the above code
a = 10
lexical.let [a:1, b:1] {p a + b}

Expected behavior: either execution of the {p a + b} block is deferred, and thus prints 2, or it is executed immediately and throws an error saying that b is undefined (since in that scope it is).

Observed Behavior: the function prints 11 as if a used the value from outside the lexical object, but waited to get the value for b.

Is this unintended behavior? Or is there some rule I'm missing?

@presidentbeef
Copy link
Owner

The block definitely grabbing the a from the scope where the block is defined. All functions are closures, so I guess this is expected?

There doesn't seem to be a way of avoiding this. Probably one of many problems with this language...

@mantacid
Copy link
Author

mantacid commented Nov 10, 2024

From my understanding of functional programming, closures don't change their behavior based on an external state. Rather, they have to be passed an argument for that value to be used. The entire reason functional programs need closures is because there is no global mutable state, and closures offer a shared mutable state in place of that global state. While brat is object oriented, and thus has global mutable state, proper closures shouldn't be affected by that state.

Perhaps it could be fixed by prefixing the compiled Lua variable names with a unique id, such that brat variables with the same name don't compile to have the same name in lua if they have different scopes.

For example: a global brat variable "foo" and a brat variable named "foo" in a closure compile like so:

foo in global  --> _g_temp1
foo in closure --> _c1_temp1

@mantacid
Copy link
Author

mantacid commented Nov 10, 2024

I have misremembered what a closure is, that's my bad. Turns out closures persist things in their lexical scope even when called outside said scope. So the closures in brat are using the scope in which they are defined as their lexical scope. The refusal to override the "a" variable thus makes sense, as the value of a from the global scope is being stored in the closure, while the value of "b" is being inherited from the lexical scope.
This doesn't explain why the "a" variable isn't being overridden in the lexical scope, but it at least explains the observed behavior.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants