Skip to content

Instantly share code, notes, and snippets.

@tsani
Last active August 29, 2015 14:21
Show Gist options
  • Select an option

  • Save tsani/8aea37d4b08033db5c93 to your computer and use it in GitHub Desktop.

Select an option

Save tsani/8aea37d4b08033db5c93 to your computer and use it in GitHub Desktop.
Python local bindings
# 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