As you may be aware, let was dropped not too long time ago from Hy. While being very integral and essential part of Lisp, it was just really hard to get working correctly and thus it was removed. However, gilch, one of core developers of Hy, didn’t give up the idea and eventually came up with a proposal how let could be written.
At the time of writing, this is still a pull request , so nothing hasn’t been merged into master yet and things could still change. But I wanted to write about this as it’s super cool feature and showcases some really neat tricks you can pull of with Hy.
If we start hy with hy –spy, REPL will output Python code. This lets us to examine what goes under the hood. Lets start with a simple example:
=> (require [hy.contrib.walk [let]])
=> (let [a 1] a)
:let_1235 = {}
:let_1235['a'] = 1
:let_1235['a']
1
Let lives currently in contrib, so it needs to be required before using. Examining spied output reveals that let is turned into a dictionary with gensymed name to avoid name collisions. After this, every usage of the given variable is through that dictionary.
Remember how lot of troubles with the original let was due to hidden function that was used to create a new lexical scope? Lets see how this new one handles that?
=> (defn nested [a b]
... (let [c (+ a b)]
... (let [c 5] c)))
def nested(a, b):
:let_1240 = {}
:let_1240['c'] = (a + b)
:let_1241 = {}
:let_1241['c'] = 5
return :let_1241['c']
We have just one function, no extra scopes or anything. Pretty cool, right? Nested lets are turned into new dictionaries and rest of the code is rewritten to use the correct one. This might sound simple, but it’s not. The macro that takes care of this is long and complicated.
What about if we try to confuse the macro and do something silly?
=> (defn tricky [a b c]
... (let [c (+ a b)] (print c))
... (let [c (* a b)] (print c))
... (print c)
... c)
def tricky(a, b, c):
:let_1239 = {}
:let_1239['c'] = (a + b)
print(:let_1239['c'])
:let_1240 = {}
:let_1240['c'] = (a * b)
print(:let_1240['c'])
print(c)
return c
Looks good to me. In each case, symbol c is coming from correct dictionary and in the end the original argument is used correctly.
I’m super happy to see this coming together and can’t wait for the pr to be merged.