6 ms·
It's been implemented, found to be subtly broken, and re-implemented a number of times. Ultimately trying to impose a different model of scoping on top of Pytho
by Kodiologist 4y ago
It's been implemented, found to be subtly broken, and re-implemented a number of times. Ultimately trying to impose a different model of scoping on top of Python's scoping is living in a state of sin; thus the difficulties. The status quo is an apparently working `let` due to a truly heroic effort by Sunjay Cauligi ( https://github.com/hylang/hy/pull/2125 https://github.com/hylang/hy/pull/2125 ). You're encouraged to try to find bugs in it.
In truth, I've never understood the fuss about `let`. It's taken a huge amount of Hy development effort, but it adds very little. Python's native scoping works well in the large majority of cases, and in tricky cases, I'd rather use `nonlocal` or `global` explicitly so I'm sure what's happening at the level of Python semantics. I suspect that people who heard that previous versions of Hy were missing `let` assumed that some other feature (such as nested lexical scopes) was also missing that has actually always been there.
- contravariant 4y agoWhat problems did you encoutner implementing 'let'? Shouldn't it be equivalent to lambda expressions somehow?
- Kodiologist 4y agoIn some other Lisps, yes, but Python has its own idea of how function scoping works that generally isn't what you'd want with `let`. For example, in (let [a 1] (defn foo [] (setv a 2)) (foo) (print a)) you probably `2` to be printed, but the seeming equivalent Python code a = 1 def foo(): a = 2 foo() print(a) prints `1`. Python interprets the assignment as declaring a new local variable. Frankly, Python got scoping wrong (in its quest to eliminate declarations), but here we are. So `let` needs to work around this somehow. The current implementation implicitly adds `global a` or `nonlocal a` as appropriate. (Actually, the name `a` is also mangled so that any references to `a` outside the `let` refer to a different variable.)
- ashtonbaker 4y agothis made me wonder - I know I can use global variables inside a function definition: a = 1 def foo(): x = a foo() so if this 'a' is a new variable, what happens when the name collides with a global variable? a = 1 def foo(): a = a foo() > UnboundLocalError: local variable 'a' referenced before assignment yuck!
- all2 4y agoI'm not sure this is so much of a problem? Changing what you name the variable fixes the issue. Python is generally quite good at having a verbose option to express things, so I'm surprised there's not an explicit way to reference the enclosing scope from an object.
- vegai_ 4y agoIs there any non-horror piece of code where supporting this latter case would be useful?
- m55au 4y agoIt is not a particularly good example. A better one would be a = 1 def foo(): x = a a = 2 foo() > UnboundLocalError: local variable 'a' referenced before assignment Basically a name cannot be both global and local inside the same function. Otherwise it would be a total mess. "a = a" just means assign the value of local variable "a" to a local variable "a", so the error makes sense.
- gilch 4y agoIt works if you disambiguate your namespaces: >>> a = 1 >>> def foo(): ... a = globals()['a'] ... print(a) ... >>> foo() 1 Still yuck?
- gilch 4y agoHere's another way to do it: >>> def foo(): ... import __main__ ... a = __main__.a ... print(a) ... >>> foo() 1
- vaylian 4y ago> In truth, I've never understood the fuss about `let`. It's taken a huge amount of Hy development effort, but it adds very little My use case is Jupyter notebooks. With normal Python scoping you quickly accumulate a lot of global variables and you might accidentally use the wrong one. With let-bindings you have variables that clean up after themselves.
- Kodiologist 4y agoI'm not familiar with Jupyter internals, but I think functions would work well for this. Hy allows multi-statement anonymous functions, and you can do stuff like `((fn [] … ))` to define and immediately call the function. One advantage of this over `let` is that it allows old locals to be garbage-collected. If memory isn't an issue, though, `let` should work well.