I'm interested in using dynamic (as opposed to lexical) scoped variables in Ruby.
It's seems there isn't a direct built-in way, as with let
in Lisp. One possible way to do dynamic scoped variable is suggested by Christian Neukirchen. He creates a "thread local hash" in his Dynamic
class. I wasn't too crazy about that.
Then I remembered that Ruby 1.9 has a tap
method. I see a lot of people using tap
to print debugging values in a chain of commands. I think it can be used to very nicely mimic a dynamically scoped variable.
Below is an an example of a situation in which one would want to use a dynamical scoped variable, and a solution using tap
.
If I had a blog to post this on, and get some feedback, I would do it there. Instead I come to S/O for criticism of this idea. Post your critique, I'll give the correct answer to the one with the most upvotes.
You have an ActiveRecord object representing an Account
, each account has_many
Transaction
s. A Transaction
has two attributes:
description
amount
You want to find the sum of all transactions
on the account
, keeping in mind that amount
can be either nil
or a Float
(no you can't critique that).
Your first idea is:
def account_value
transactions.inject(0){|acum, t| acum += t.amount}
end
This bombs the first time you have a nil amount:
TypeError: nil can't be coerced into Fixnum
Use tap
to temporarily define amount = 0
. We only want this to be temporary in case we forget to set it back and save the transaction
with the 0 value still in place.
def account_value
transactions.inject(0){|acm, t| t.amount.tap{|amount| amount ||=0; acm+=amount}; acm}
end
Since the assignment-to-zero-if-nil of amount
is within the tap
bock, we don't have to worry about forgetting to set it back to nil
.
What are your thoughts?
Well, I think you're aiming for something else, but the following code fixes your example and is actually easier to understand:
transactions.inject(0) { |acum, t| acum += t.amount || 0 }
But I don't don't think the method summing up the amounts should know about the default value for nil
amounts, so (even if your question states that I can't argue with it) I would change the amount
method to return the default instead:
def amount
@amount || 0
end
Nevertheless I think your example is just too easy to solve and you're actually aiming for answers to a more complex question. Looking forward to all the other answers.