opshin
opshin copied to clipboard
Figure out which statemonad is passed exactly to a function in python
Current experiments suggest that a function has always access to the local scope it is defined in. This includes its own definition. Recursion is only implemented by a function accessing this version of itself through the statemonad.
def a():
a()
b = a
del a
b()
results in a NameError.
However the variables of a local scope unknown to the callee are still accessible
def a():
def b():
print(d)
return b()
d = 1
return b
def c():
g = a()
g()
c()
Prints 1 until the recursion depth is exceeded.
With the aggressive type inferencer, it might be sufficient to give a function access to all variables defined in its enclosing state + itself. This might however break this (admittedly weird) behavior:
def a():
d = 0
def b():
print(d)
d = 1
return b
def c():
g = a()
g()
c()
prints 1
As long as functions can not be returned by functions, this is not an issue. And since functions are currently no valid types, this is the case
This is actually an issue as demonstrated by this case
def validator(_: None) -> int:
a = 2
def b() -> int:
return a
def c() -> int:
a = 3
return b()
return c()
Python returns 2 here, while OpShin returns 3. Potential remedies include two paths
- forbid shadowing of variables (entirely or in specific cases)
- properly bind the own statemonad to a function when calling it instead of binding the current scope statemonad
Maybe the latter can be implemented by keeping track of different statemonads based on the stack depth and each function requesting a specific statemonad.
Another potential fix: annotate every variable with the scope depth in which it is defined. This fixes the issue as long as we don't allow passing functions around
The solution should be somewhat like this: In a preliminary rewrite, every scope is given a unique scope id. Then, every variable name is changed to Store -> variable_name_current_scope, Load -> variable_name_scope_in_which_var_is_defined.
Note that for the load assignment we need to keep track of all variables, even those defined after the current definition as this will define their "visible scope". Example
>>> a = 1
>>> def c():
... def d():
... return a
... print(d())
... a = 2
... return d()
...
>>>
>>> c()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in c
File "<stdin>", line 3, in d
NameError: free variable 'a' referenced before assignment in enclosing scope
Even though there is a visible a for d at time of its first call, it explicitly points to the a defined in the enclosing scope, which is only defined after it is called.