
Abusing Python exceptions to turn a recursive function iterative - mercapto
https://gist.github.com/razimantv/1b33d4a090a5bc9ed94928012b37c3f0
======
Demiurge
Abusing python can really save your life sometimes. I think my favorite recent
hack has been accessing and MODIFYING callers variable, due to inability to
get it passed through the Django module to the callback.

    
    
      def invite_accepted(sender, email, **kwargs):
          import sys
          caller = sys._getframe(2)
          request = caller.f_locals['request']
          request.session['invite_accepted'] = True

~~~
Waterluvian
I love how _everything_ is available at runtime. Python's "we are all
consenting adults here" philosophy has been super useful in a pinch.

I was monkey patching an external library in JavaScript today, rather than
having to fork the thing for one slight modification, and thought about this
philosophy again.

~~~
btilly
Monkey patching is something that you should know _how_ to do. And then learn
_not_ to do.

Monkey patching is how you get into situations where the order in which
libraries get loaded causes software to unpredictably work or break. Or, worse
yet, to work reliably on the developer's machine and break in the wild.
(Because the external library on someone else's server loads slower for the
developer than their local library, and faster for some users.)

~~~
Waterluvian
Never say never. I saved hours on proof of concept code that dies in weeks.

I think you learn _when_ not to do it, which is most of the time.

~~~
RcouF1uZ4gsC
> dies in weeks.

Code can end up living a lot longer than people think it will (see Y2K bug).

~~~
rrcaptain
>see Y2K bug

One of my biggest pet-peeves is how since popular media hyped Y2K up as a
potential world ending disaster when nothing happened people thought the whole
thing was a myth. When it was not a myth (though it was overblown), it was
just avoided because people engineered around it. I can't wait to see how
people handle the Year 2038 Problem.

------
omginternets
This isn't abuse.

 _This_ is abuse:
[https://github.com/ajalt/fuckitpy](https://github.com/ajalt/fuckitpy)

~~~
dingo_bat
> The web devs tell me that fuckit's versioning scheme is confusing, and that
> I should use "Semitic Versioning" instead. So starting with fuckit version
> ה.ג.א, package versions will use Hebrew Numerals.

Heh.

~~~
golergka
ה.ג.א is 5.3.1, not 4.8.1:

[https://github.com/ajalt/fuckitpy/blob/master/setup.py#L16](https://github.com/ajalt/fuckitpy/blob/master/setup.py#L16)

~~~
jwilk
It doesn't use SV yet (as evidenced by the fact it's stil using Arabic
numerals).

------
dfee
I couldn't read this on my phone – it seems GitHub doesn't format Jupyter
notebooks on mobile Safari at least. Here's a mobile readable gist:
[https://gist.github.com/dfee/00c1d7e70abfdbc3ef03d8ec165a104...](https://gist.github.com/dfee/00c1d7e70abfdbc3ef03d8ec165a1043)

~~~
anomie31
Or even better, here it is on the online jupyter notebook viewer.
[http://nbviewer.jupyter.org/gist/razimantv/1b33d4a090a5bc9ed...](http://nbviewer.jupyter.org/gist/razimantv/1b33d4a090a5bc9ed94928012b37c3f0)

------
dfox
On somewhat similar note dfsch implements tail calls by essentially throwing
C-level exception (the VM contains somewhat ugly C macrology that implements
exception handling in C in a way that is somewhat similar to WinAPI's
exceptions, this is then used to implement TCO, tagbody and condition system).
The implementation is arguably ugly, but the end result is that anything that
you can do in scheme code you can also do in C native functions.

------
Karunamon
I've got a sneaking suspicion this is one of those clever, elegant, kind-of-
awesome-thanks-python, yet _entirely evil_ things you can, but never actually
should do in a production app.

Is there a use case for something like this?

~~~
arghwhat
Absolutely not.

If you want an iterative function, write one. It's not harder than writing a
recursive one (although sometimes a _bit_ less readable).

Writing an iterative function is also both faster than the recursive function,
and _much_ faster than the hack.

Still a neat hack, though.

~~~
odonnellryan
Well, sometimes it is harder. Think, any problem related to traversing a tree,
or similar problems related to trees and/or graphs.

~~~
chronial
You can just replace the call stack with an explicit stack in your function –
that should never be hard, just a bit less readable :).

~~~
odonnellryan
Yes I can see how this makes sense, but I wouldn't say it is easier than
recursion! Maybe a good exercise, but when traversing a tree the recursive
solution is very easy to understand. The iterate solution ...

Then you have other problems, where the recursive solution is actually not
super easy to understand why it "works" immediately (think, balanced paren
generation) and going on to create an iterative solution, while not insanely
hard, is not something super obvious.... what's your condition to stop
iteration, again?

Maybe I'm thinking about that the wrong way, I'd love to learn more about it!

That problem is closely related to tree traversal, anyway, I should fool
around with those two ideas a bit more to properly understand.

~~~
arghwhat
I never said it was _easier_ , I just said it was not much _harder_. :)

Like I said, it is sometimes a bit less readable (tree-walking is a good
example of such case), but at the same time, what the code actually does is
much clearer. You have no hidden costs, and the code is easier to optimize
this way.

You mention a case where you do not fully understand why the recursive
solution works, in which case you obviously can't easily write an iterative
solution. However, in this case, you are poorly equipped to make _any_
implementation, recursive or iterative.

------
rachitnigam
This is a cool hack. Our project (Stopify.org) can do this transformation on
source JS programs and _automatically_ give you heap bounded stacks
transparently.

A cool result of doing this in JavaScript is that any language that compiles
to JavaScript (python, ocaml, Scala, c++, clojure...) can _transparently_ get
heap bounded stacks by just using our compiler.

For some more examples, look at pyret (pyret.org) that also supports heap
bounded stacks. (In fact, we were able to strip out the Pyret compiler and
just use Stopify to get all these benefits)

------
bcbrown
Here's a neat bit of Python abuse I stumbled across recently... you can define
a method to return itself:

    
    
        >>> def recurse():
        ...   return recurse
        ...
        >>> recurse
        <function recurse at 0x1083b5f28>
        >>> recurse()
        <function recurse at 0x1083b5f28>
        >>> recurse()()()
        <function recurse at 0x1083b5f28>

~~~
abhishekjha
Id don't know when this us useful.

Also, what does `recurse()()()` mean? What are we looking for exactly?

~~~
fwdpropaganda
Means "take the function recurse, execute it, then take the thing it returns
and execute that, and then take the thing that returns and execute that."

~~~
abhishekjha
Any scenario where it is useful to do so?

------
phyzome
I haven't done more than skim the code, but is this basically unrolled
trampolining via exceptions?

~~~
teraflop
It's a little more devious than that. The key is that the decorator maintains
a stack of call parameters, and a map from parameters to results.

When a recursive call happens, it aborts the top-level call, runs the
recursive call directly, memoizes the result, and then _re-executes_ the
original call. Hence the warning about this only being usable for pure
functions.

EDIT: also I just noticed that the map keys aren't the parameters themselves,
but their _string representations_. So heaven help you if you try this with a
data type whose str() method isn't one-to-one.

~~~
xapata
Better to use the method demonstrated in functools' lru_cache:
[https://github.com/python/cpython/blob/3.6/Lib/functools.py#...](https://github.com/python/cpython/blob/3.6/Lib/functools.py#L421)

------
bageldaughter
This reminded me of an abomination I made a little while back allowing you to
simulate haskell List monad behavior by decorating a generator function.
[https://gist.github.com/spitz-
dan-l/d305ee410e1393a4df19065b...](https://gist.github.com/spitz-
dan-l/d305ee410e1393a4df19065b94d6f51d)

------
agumonkey
My internship (trying to compile graphs into cobol) involuntarily ended up
abusing java exception because I didn't know how to write graph rewriting
systems, induction and backtracking.. I think my employer never ever used
that.

------
tempodox
I just can't help thinking of [https://blog.codinghorror.com/monkeypatching-
for-humans/](https://blog.codinghorror.com/monkeypatching-for-humans/)

------
thr0000waay
note to self: python ppl discovered unwind-protect in 2018.

~~~
icebraining
Nope, we've had "finally:" (the equivalent of unwind-protect) for decades. And
this code doesn't use it.

~~~
thr0000waay
Good. Now discover call/cc.

~~~
icebraining
No thanks, it's a terrible control structure. We already have coroutines for
doing most of what you can do with call/cc without its problems.

------
kitd
OT, but as a non-Pythonista, I very much appreciate the way Python does
function annotations. Neat and obvious!

------
nhumrich
Or you could just use generators

