Last active
August 29, 2015 14:21
-
-
Save tsani/8aea37d4b08033db5c93 to your computer and use it in GitHub Desktop.
Python local bindings
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # It's tough to give a truly temporary name to a value in Python. | |
| # Of course, we can just do something like | |
| t = my_complicated_expression | |
| v = do_stuff_with(t) + do_different_stuff_with(t) | |
| # and now "t" has whatever value "my_complicated_expression" evaluates to, | |
| # but this requires a separate statement altogether, which makes reusing | |
| # the result of a complicated evaluation several times in a lambda function | |
| # impossible. | |
| # The solution then is to use a lambda expression in the first place. | |
| v = (lambda t: do_stuff_with(t) + do_different_stuff_with(t))(my_complicated_expression) | |
| # but this reads backwards; we aren't sure what "t" is referring until much later. | |
| # Really, what we'd like is to mimic Haskell's "let"-bindings. For example, | |
| # > let t = myComplicatedExpression in doStuffWith t + doDifferentStuffWith t | |
| # so we introduce the "let" function in Python, which is just | |
| # function application in reverse. | |
| def let(x, f): | |
| return f(x) | |
| # Now we can write | |
| v = let( | |
| my_complicated_expression, | |
| lambda t: do_stuff_with(t) + do_different_stuff_with(t)) | |
| # In Python 3, we can easily extend this to allow for multiple simultaneous | |
| # bindings. | |
| def let(*xs, in_): | |
| return in_(*xs) | |
| # In the above, the "in_" argument is keyword-only, so we have to write | |
| v = let( | |
| my_complicated_expression, | |
| in_=lambda t: ...) | |
| # but I think that's fine. Really, it improves readability in my opinion. | |
| # In Python 2, however, we can't use keyword-only parameters; they're a | |
| # Python 3-specific feature that hasn't been backported. So we're left | |
| # with two alternatives. | |
| # 1. We can simulate keyword-only parameters by using double-star-args, or | |
| # 2. We can forego the idea of keyword-only parameters altogether by | |
| # considering that the last element of the star-args list is the function. | |
| def let1(*xs, **k): | |
| return k['in_'](*xs) | |
| def let2(*xs): | |
| return xs[-1](*xs[:-1]) | |
| # I believe that the latter is better since it avoids the possibility | |
| # of spurious or accidental keyword arguments being added to the argument list. | |
| # Here's a one-liner lambda version of the function for you to include in your | |
| # projects. Of course, the downside of the lambda is that the stack traces become | |
| # nastier in case of exceptions. | |
| let = lambda *xs: xs[-1](*xs[:-1]) | |
| # Also, I'd recommend against using this is any code that is meant to be read by | |
| # others or in code that is performance-critical. (Then again, why are you using | |
| # Python for performance-critical code in the first place?) | |
| # You get performance hit due to three places: | |
| # 1. calling the let function itself requires a stack frame. | |
| # 2. moving your complicated expression into a lambda requires constructing a closure. | |
| # 3. calling your lambda introduces another stack frame. | |
| # Of course, using this let function without a lambda in the last argument is | |
| # pointless; you might as well just call the function directly. | |
| def my_function(x): | |
| return x + 1 | |
| let(complicated_expression, my_function) | |
| # why this^ instead of: | |
| my_function(complicated_expression) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment