We had a great meeting last night for the RubyGorge Block Party. Pasta, beers, great talk, and programming. What more could a guy want? (Wait, don’t answer that… “No, honey! I–” <smack!>)
We stumbled a bit on our discussion of blocks, mostly because I threw Jason to the wolves (“Uh, so let’s start. Jason, go!”). Partly, it was because blocks are such an unusual concept to non-Ruby programmers, that it’s just hard to wrap your head around them.
So, I thought I’d write a few posts on Lambdas and Blocks– mostly so I can work through some things to try to understand them myself. Because I’ve been a Pythonista for so long, I thought I’d try to discuss Ruby blocks by describing them in terms of Python code. Strange idea? Of course, but let’s see how it goes.
Let’s start with a focus on lambdas, and let’s start in full-on Python code.
Building a Stream Reach
There are times that you want to access all the elements in an array, and perhaps operate on the attributes of those elements, instead of the elements themselves. To wit:1
from __future__ import division # if using Python 2.6, let's divide to floats
import random
class StreamSection:
def __init__(self,l,w,d,s):
self.length = l
self.width = w
self.depth = d
self.slope = s
def __repr__(self):
return "SS:(%i, %i, %i, %0.3f)" % (self.length,
self.width, self.depth, self.slope)
reach = []
for i in range(10):
l = 50
w = random.randint(5,10)
d = random.randint(1,5)
s = random.random()
reach.append(StreamSection(l,w,d,s))
print(reach)
>>> [SS:(50, 10, 3, 0.252), SS:(50, 5, 1, 0.843), SS:(50, 8, 3, 0.723),
SS:(50, 7, 2, 0.548), SS:(50, 8, 2, 0.573), SS:(50, 6, 2, 0.218),
SS:(50, 9, 3, 0.149), SS:(50, 6, 1, 0.555), SS:(50, 6, 5, 0.425),
SS:(50, 8, 2, 0.088)]
So here we create a stream reach comprised of 10 rectangular stream channel sections. Each section has a length of 50 meters, a random width, depth and slope.2 Let’s say we want to come up with some average values for width and length. One way to do this is:
def average(lst): return sum(lst)/len(lst) widths = [] depths = [] for sec in reach: widths.append(sec.width) depths.append(sec.depth) ave_w = average(widths) ave_d = average(depths) print(ave_w, ave_d) >>> (7.2999999999999998, 2.3999999999999999)
What we do here is create a list of widths and depths, then average those lists. We need to iterate over the reach to accomplish this because we need to access the width and depth of each section (Of course, Python has list comprehension, but that’s something we’ll get into in a bit).
Intro To Lambda
What we want to explore is a way to do this without the need for the steps of “create a function, iterate through the list, build a new list, then send that new list to the function.” This is the perfect use for a lambda.
A lambda is a “nameless function.” It’s a function that can be passed around like an object, need not be defined before hand, and is garbage collected automatically when we leave the scope. Lambdas are like really stupid, super simple, one-line functions. For example here are two really simple lambdas:
def go: first = lambda x: int(x) second = lambda x,y: x*y print(first(2.3)) print(second(2, 3)) go() >>> 2 >>> 6 first(3.5) >>> ERROR! Undefined variable 'first'
Lambdas take arguments and do one line’s amount of stuff to them, automatically returning the result. For instance, the following is equivalent to the average() function above:
ave = lambda x: sum(x)/len(x) print(ave(widths), ave(depths)) >>> (7.2999999999999998, 2.3999999999999999)
While this functionality isn’t really too useful here, it becomes moreso when we start to combine it with other functionality.
For instance, Python’s map() function applies a function to each element in an iterable collection. To use map(), we need to call it as map(function, collection). This means that we need to pass a function into the call itself. We can write a function, store it, and then send that, of course, but sometimes we just want to do something quickly and throw it away. With lambdas, we can create temporary nameless functions on the fly.
List Comprehension
Previously, when we wanted a list of stream widths, we needed to create an empty list, iterate over the entire reach, and pull the width from each reach, adding it to the empty list of widths. Now, we can use map() to apply a lambda function to the entire list, and that lambda function can simply return the “width” attribute of each element:
widths = map(lamba x: x.width, reach) print(ave(widths)) >>> 7.3
It turns out that this is so powerful a concept, that Python implemented list comprehension, which is essentially a way of doing this same thing:
[i.width for i in reach] >>> [10, 5, 8, 7, 8, 6, 9, 6, 6, 8] print(ave([i.width for i in reach])) >>> 7.3
Making it Zippy
Using this nameless function, we can forgo the step of creating methods just to do something simple once. We can also do a lot in a single line of code. For instance, we can use Python’s zip() method to really reduce our code. zip() returns a list of tuples where the i-th tuple is the i-th element of each tuple. From the Python documentation:
>>> x = [1, 2, 3] >>> y = [4, 5, 6] >>> zipped = zip(x, y) >>> zipped [(1, 4), (2, 5), (3, 6)] >>> x2, y2 = zip(*zipped) >>> x == list(x2) and y == list(y2) True
So, let’s add zip() to our new understanding of map() and lambda, then print the averages of our stream reach in one line. Yes, all the code from our first average attempt in a single line:
print map(lambda x: sum(x)/len(x),zip(*map(lambda x: (x.width, x.depth), reach))) >>> [7.2999999999999998, 2.3999999999999999]
Scary? Not so! Let’s look at it like maths and start innermost parentheses and working out. I’ll describe each element, and then replace that element with ellipses (…) in the following element. This way, you can build it up yourself and try each part in IDLE or the commandline:
- Use lambda to give us a tuple of element.width and element.depth for whatever duck-typed element we send it: (lambda x: (x.width, x.depth))
- Use map() to apply that nameless lambda function to the entire list of stream reaches (map(…, reach))
- Use zip(*args) to take the resulting list of two-element tuples and return two lists of the elements (i.e. a list of widths and a list of depths) (zip(*…))
- Then we create another nameless lambda function (lambda x: sum(x)/len(x))
- Finally, use map() again to apply the second lambda to whatever is returned from zip() (map(…, zip(*…)))
Coda
So, here we see the power of lambdas: nameless, temporary functions that get immediately garbage collected upon leaving scope. It helps Python become a much more functional language. When programming in Python, I tend to use lambdas (along with map() and zip()) pretty much all the time. Whatever I’m doing, I find the need to iterate over the elements in a list and perform some operation on them, and there’s always a need for these non-reusable bits of code that I use to perform those operations.
Recently, I’ve learned that while lambdas are something I used daily in Python, they’re something I will very rarely use in Ruby. This is because while lambdas are something that were added to Python, they are something fundamental to Ruby. I’ll discuss that in the next post, where I try to write an example to help me wrap my head around blocks.


