> The IIFE/outer closure here is equivalent to the capture happening in rust. So this is more equivalent to the rust examples than your python example.
Wait, I don't follow. Rewriting your example to only use one lambda for clarity, we have:
def mystery_closure_one_lambda():
x = 1
def capture(v):
return lambda: v
closure = capture(x)
x = 2
return x * closure()
So notice the lambda (i.e. what we are assigning to the variable 'closure') is now capturing v, not x, which is why it doesn't see the change we make to x, i.e., why it returns 2 instead of 4.
But this is not equivalent to the rust code! There is no v at all in rust. We are capturing x! (It's slightly obscured by the fact that we to use an unsafe ptr to defeat the borrow checker, but we are still capturing x.)
So I do not think mystery_closure is equivalent to either of the rust mystery_a or mystery_b above; it is in fact equivalent to this:
fn mystery_closure() -> i32 {
let mut x = 1;
let closure = (|v| move || v)(x);
x = 2;
x * closure()
}
Which also returns 2, just like the python code. (It's also a direct translation of the python code!)
> Let's start in python, since python doesn't allow multiline lambdas, we have to swap to using an inner function, which is fine, this makes the structure a bit clearer in python.
Careful! -- your de-lamba-fication accidentally changed the semantics. If we just de-lambda-fy, we get:
def mystery_int():
x = 1
def closure():
def inner(v):
return v
return inner(x)
x = 2
return x + closure()
Which returns 4, showing it's defnly not equivalent. The correct de-lambda-ficiation is:
def mystery_closure_no_lambas():
x = 1
def capture(v):
def inner():
return v
return inner
closure = capture(x)
x = 2
return x * closure()
(which as a sanity check, returns 2, as it should).
Bringing in mutable reference data types like vec is I think not really relevent to what's at play here.
In both rust and python, the non-reference types mut i32 (rust) and int (python) are mutable. In rust you can pass a mutable reference to an i32, and in python you can't, but so what; that's not really relevent.
DIGRESSION:
Just for funsies, you actually can achieve what are essentially mutable references in python3 (you could also do this in py2 if you wanted to get nasty with locals()):
# a mutable reference to a local variable
class Ref:
def __init__(self, getfn, setfn):
self.getfn, self.setfn = getfn, setfn
def get(self): return self.getfn()
def set(self, v): self.setfn(v)
value = property(get, set, None, "reference to local variable")
# change a local variable using the mutable refernce
def mutate(ref, new_value):
ref.value = new_value
def mystery_py_mutable_ref():
x = 1
# get a mutable reference 'ref' to x
def get():
return x
def set(v):
nonlocal x
x = v
ref = Ref(get, set)
# capture x in a closure
capture = lambda: x
# mutate x
mutate(ref, 2)
# finally evaluate x and the closure; this will return 4!
return x * capture()
END DIGRESSION
But anyway, I don't think it's actually relevent here.
Question: Are you familiar with scheme? Would you agree or disagree that the following scm_mystery_a and scm_mystery_b are equivalent to the rust mystery_a and mystery_b functions?
> So notice the lambda (i.e. what we are assigning to the variable 'closure') is now capturing v, not x, which is why it doesn't see the change we make to x, i.e., why it returns 2 instead of 4.
Yes, but this goes back to the scoping issue: in python, lambdas (and functions in general) don't capture. The only way to close over something is to pass as an argument. So to get the lexical closure behavior that rust provides, you have to add extra stuff in the python. This indeed makes the translations not mechanical (and you can add the lambda back in the rust, it doesn't hurt anything in these examples), but to get matching scoping behavior between rust and python, you need an extra layer of indirection in the python.
> Bringing in mutable reference data types like vec is I think not really relevent to what's at play here.
Of course it is, because in python everything is a reference. There's no such thing as a value type, and this is precisely where the difference in behavior comes in (other than the scoping issues). A rust RefCell is the thing that most naturally matches the actual in memory representation of a PyObject.
As for your digression, eww, although you forgot to actually do the sneaky part. This would be the actual demonstration, you need to modify the list in the closure (a real closure), and set it after the closure is created and before it is evaluated:
# capture x in a closure
def closure(v):
def inner():
mutate(v, 2)
return v
return inner
capture = closure(ref)
ref.set([3])
Yes your digression example works, but it works equally well without a mutable ref. You need to mutate the thing in the callback (and to have the callback actually close over the ref) to require mutable ref semantics.
And yes, python closures don't capture environemtnal vars like rusts do. If you need them to capture env vars, you have to do what I did in the example.
Wait, I don't follow. Rewriting your example to only use one lambda for clarity, we have:
So notice the lambda (i.e. what we are assigning to the variable 'closure') is now capturing v, not x, which is why it doesn't see the change we make to x, i.e., why it returns 2 instead of 4.But this is not equivalent to the rust code! There is no v at all in rust. We are capturing x! (It's slightly obscured by the fact that we to use an unsafe ptr to defeat the borrow checker, but we are still capturing x.)
So I do not think mystery_closure is equivalent to either of the rust mystery_a or mystery_b above; it is in fact equivalent to this:
Which also returns 2, just like the python code. (It's also a direct translation of the python code!)> Let's start in python, since python doesn't allow multiline lambdas, we have to swap to using an inner function, which is fine, this makes the structure a bit clearer in python.
Careful! -- your de-lamba-fication accidentally changed the semantics. If we just de-lambda-fy, we get:
Which returns 4, showing it's defnly not equivalent. The correct de-lambda-ficiation is: (which as a sanity check, returns 2, as it should).Bringing in mutable reference data types like vec is I think not really relevent to what's at play here.
In both rust and python, the non-reference types mut i32 (rust) and int (python) are mutable. In rust you can pass a mutable reference to an i32, and in python you can't, but so what; that's not really relevent.
DIGRESSION:
Just for funsies, you actually can achieve what are essentially mutable references in python3 (you could also do this in py2 if you wanted to get nasty with locals()):
END DIGRESSIONBut anyway, I don't think it's actually relevent here.
Question: Are you familiar with scheme? Would you agree or disagree that the following scm_mystery_a and scm_mystery_b are equivalent to the rust mystery_a and mystery_b functions?