Clarifying lambdas

Blocks in Ruby are everywhere. They’re one of the language’s distingushing features, and allow you to deploy tiny snippets of anonymous code without needing to encase them in big old method definitions.

But this anonymous quality can be a hindrance as well as a help; it can cloud your intentions. In Ruby, as in most languages, we’re encouraged to refactor code into well-named methods that reveal your intentions, and there’s a point where this is true of blocks too.

Today I found myself, as I often do, working with a huge collection that I needed to trim down to size. As ever, I turned to my trusted chums reject, group_by, and sort_by, and soon had things where I wanted them:

unvalued_projects = workspace.projects(:all, filter: "is_done is false")
  .reject { |p| p.contract_value }
  .group_by { |p| members[p.owner_id] }
  .sort_by { |o, p| p.length }
  .reverse

But those blocks, for me at least, give me pause. I know what they mean now: but will I if I revisit this code in six months?

If this code were elsewhere, I’d be thinking about pulling it into methods; even if the methods only contain a single line, their names convey their purpose and give the code that literal, English-language, readable quality that makes Ruby so clear and refreshing.

Well, blocks are no different: everywhere you can use a block, you can use a lambda; lambdas have names; and so, by replacing our blocks with lambdas, we can achieve the same intention-revealing effect:

has_contract_value = ->(project)  { project.contract_value }
number_of_projects = ->(projects) { projects.last.length }
owner_name         = ->(object)   { members[object.owner_id] }

unvalued_projects = workspace.projects(:all, filter: "is_done is false")
  .reject(&has_contract_value)
  .group_by(&owner_name)
  .sort_by(&number_of_projects)
  .reverse

For me, those chained methods now read more easily and reveal their intentions more readily. I’m more comfortable with the idea that I’ll still understand this code if I revisit it in six months’ time.

The downside, of course, is that we’ve inflated our code by three lines; the question of whether that’s worth it is one that you’re best placed to answer in your own code. Sometimes I reach for this technique; sometimes I leave things as blocks. But it’s a useful tool to have in your arsenal, at any rate.