opshin icon indicating copy to clipboard operation
opshin copied to clipboard

Figure out which statemonad is passed exactly to a function in python

Open nielstron opened this issue 2 years ago • 4 comments

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

nielstron avatar Jan 11 '23 00:01 nielstron

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

nielstron avatar Jan 11 '23 14:01 nielstron

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.

nielstron avatar Apr 23 '23 09:04 nielstron

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

nielstron avatar Apr 23 '23 10:04 nielstron

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.

nielstron avatar Apr 24 '23 14:04 nielstron