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.
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.
(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.)But maybe it is because I am familiar with Clojure (where `setv` or equivalents doesn't exist) and not other Lisps.
This would be the equivalent Clojure code anyway:
user=> (let [a 1] (defn foo [] (def a 2)) (foo) (println a))
1
nil
So yeah, makes sense for me. But can understand why some other people would expect the other way though. * (let ((a 1))
(defun foo ()
(setq a 2))
(foo)
(print a))
2
2 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! >>> a = 1
>>> def foo():
... a = globals()['a']
... print(a)
...
>>> foo()
1
Still yuck? >>> def foo():
... import __main__
... a = __main__.a
... print(a)
...
>>> foo()
1Python 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.
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.
def f():
a = 1
def g():
nonlocal a
a = 2
g()
print(a)
though nonlocal can't be used when referring to global variables... it's kinda bad.