Copyright © 2004–2010 OpenSourcery, LLC. This work is licensed under a Creative Commons Attribution 3.0 United States License.
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
In a Rails project we're currently developing, I ran into an odd failure while running rake:
$ rake gems:unpack (in /home/hobson/railsproject) rake aborted! undefined method `[]' for :sourdough:Symbol /home/hobson/railsproject/lib/tasks/import.rake:113:in `method_missing' ...
Why does this trace end in a totally unrelated rakefile? A look at the source of this file provided a simple explanation: in order to break up the logic of a complicated Rake task, someone had defined a large number of constants and utility methods (including method_missing!) without a surrounding class or module namespace. Other Rake tasks were accidentally triggering this method_missing implementation because it was defined at the top level. This is why it's never a good idea to define methods in the top level of any Ruby file that might be included as part of a larger system. The solution is to wrap the methods in a module, and include that module in the task namespace:
module ImportUtil # you can define any method you want here, including method_missing. end namespace :import do include ImportUtil task :something do # you can now call everything defined in ImportUtil as a top level method end end
This avoids poisoning the top level of your Ruby environment with Rake utility methods. The lesson here is that since Ruby makes it so easy to include modules at any level, it's almost never necessary to define a method at the top level. Especially not method_missing.
UPDATE: Whoops, it turns out the solution I proposed above does not work. Since rake "namespaces" are really just blocks executed at the same level at which they're declared, the module include causes the methods to be included into the top level anyway. While the final lesson of this post remains true, the only solution I'd recommend (and the one we eventually took with the code above) is to remove the offending code completely, and don't ever define method_missing (or any other method, if you can help it) at the top level.
Tagged as: Rake, Ruby, Ruby on Rails