Object#let in Ruby

A common construct in functional programming languages is the let macro, used to define lexically-scoped names. The let macro exists in most (all?) Lisp dialects and spiritual descendants, including Clojure. Since it's possible to define lexically-scoped variables at any time in Ruby, there isn't much perceived need for a construct like let. However, Ruby's block syntax makes it particularly easy to experiment with functional programming techniques, and in fact there is already a method in Ruby's standard library that is maddeningly similar to let, the tap method.

The reason I assert that tap is "maddeningly similar" to let is that tap does not return the result of the block, rather, it always returns the receiver. This makes it possible to take a value or values and create lexically-scoped names referring to them in a block, but there is no way to access the results of that block unless you explicitly stash them in the receiver or (possibly) a global. Even still, tap has its uses, which is why it's been in the standard library since 1.9.

# output: up is down
"down".tap { |up| puts "up is #{up}" }

A Ruby implementation of let would be similar to tap in that it would allow you to create a lexical block, but it would differ in that it evaluates to the block result:

# output: up is down
puts "down".let { |up| "up is #{up}" }

In cases where you don't care about the result of the block you can use either one, but if you care about the block result instead of the receiver then you can use let instead of tap:

# output: UP IS DOWN
puts "down".let { |up| "up is #{up}" }.upcase

I find myself wanting to use tap or let in cases when I need to use a calculated value multiple times. I could just create a temporary variable, of course:

c = some_complex_expression
do_something if c
other_method(c)

Instead I can use tap (or let if it's available) to achieve the same effect:

(some_complex_expression).tap do |c|
  do_something if c
  other_method(c)
end

This is quite useful in ERB templates, where creating temporary variables feels especially wrong.

You can also exploit a feature of Ruby to get multiple lexical names per block. If the receiver is an Array and you provide multiple names in the block arguments, the contents will automatically be broken out. This works with either tap or let:

(1..3).to_a.tap do |one, two, three|
  puts "one is #{one}"
  puts "two is #{two}"
  puts "three is #{three}"
end
 
puts (1..3).to_a.let { |one, two, three|
  "one is #{one}", "two is #{two}", "three is #{three}"
}.join("\n")

The implementation of both methods is trivial. It's not like any developer who wants to use let needs to wait for some Ruby core developer to code it up for him. Here's the complete implementation of Object#let:

class Object
  def let
    yield self
  end
end

The tap method (which is already in the standard library) is about twice as large, weighing in at two trivial lines:

class Object
  def tap
    yield self
    self
  end
end

To other developers that stumble across this, I pose this question: what techniques and/or idioms do you favor when a scoped temporary variable is called for?

Tagged as: Ruby

3 comments

Adam (not verified) wrote 1 year 41 weeks ago

Very Informative Blog

Hi,
It is great to come to your about, i have been learning to build website by using Drupal, your blog posts are very informative for to keep on reading.
Thanks

Shane (not verified) wrote 1 year 39 weeks ago

Thanks

Thanks for the great info here!
==========================

Matthew Bellantoni (not verified) wrote 44 weeks 2 days ago

Wish I had let

Once I discovered the existence of #tap I've started using it more and more, to the point that I've now discovered I also want #let. As you state, the more I write Ruby the more using intermediate variables feels wrong.