Description
Conditionals containing statements are doing unnecessary work. I know, Python is not renowned for its performance, but let's not make it worse. This kind of thing can matter inside nested loops.
Currently a cond
like this:
(cond [0 "zero"]
[1 "one"]
[True (assert 0)])
Turns into this:
if 0:
_hy_anon_var_3 = 'zero'
else:
if 1:
_hy_anon_var_2 = 'one'
else:
if True:
assert 0
_hy_anon_var_1 = None
else:
_hy_anon_var_1 = None
_hy_anon_var_2 = _hy_anon_var_1
_hy_anon_var_3 = _hy_anon_var_2
But wouldn't it would work just as well like this?
if 0:
_hy_anon_var_1 = 'zero'
elif 1:
_hy_anon_var_1 = 'one'
elif True:
assert 0
_hy_anon_var_1 = None
Half the assignments for the same effect. And the hy --spy
output is much more readable. I don't think CPython can automatically optimize this kind of thing. I know not all of them happen for any given branch, but the deeper ones will still do more assignments than they should.
Python has no switch/case. Sometimes you can get away with dictionary lookups instead, but if you need side effects or performance, you're supposed to use cascading elif
where you would have used a switch.
Currently there's no elif
form in Hy. cond
is the closest we've got. Now I could write nested if
s instead (or a macro to do it for me) but look at what it turns into:
=> (if 0 "zero"
... (if 1 "one"
... (assert 0)))
if 0:
_hy_anon_var_2 = 'zero'
else:
if 1:
_hy_anon_var_1 = 'one'
else:
assert 0
_hy_anon_var_1 = None
_hy_anon_var_2 = _hy_anon_var_1
Not quite as bad as the cond
since we have a final else
, but still twice what is necessary:
if 0:
_hy_anon_var_1 = 'zero'
elif 1:
_hy_anon_var_1 = 'one'
else:
assert 0
_hy_anon_var_1 = None
Maybe it's too hard to optimize nested if
forms like that? Is cond
defined in terms of Hy's if
form in the first place? I didn't implement cond
but you could totally do it as a macro in terms of if
.
Why not extend Hy's if
to work like Arc Lisp's (as I proposed in #830 ), which can accept two or more arguments:
=> (if a b)
if a:
_hy_anon_var_1 = b
else:
_hy_anon_var_1 = None
=> (if a b
c)
if a:
_hy_anon_var_1 = b
else:
_hy_anon_var_1 = c
=> (if a b
c d)
if a:
_hy_anon_var_1 = b
elif c:
_hy_anon_var_1 = d
else:
_hy_anon_var_1 = None
=> (if a b
c d
e)
if a:
_hy_anon_var_1 = b
elif c:
_hy_anon_var_1 = d
else:
_hy_anon_var_1 = e
etc., for any number of inner elif
s you need.
- Now you only have to generate AST for the new
if
form, instead of optimizing nested forms. - It make Hy's
if
act more like Python's cascadingelif
. - This doesn't break existing Hy code, since we're not using more than 3 arguments now, and the 2 and 3 argument cases work exactly like before.
cond
can be redefined in the same way, and get the same benefits.