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

No comments

Add your comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>. Beside the tag style "<foo>" it is also possible to use "[foo]".

More information about formatting options