Ruby 1.9 中的动态变量作用域
我对在 Ruby 中使用动态(而不是词法)作用域变量感兴趣。
似乎没有直接的内置方法,就像 Lisp 中的 let
一样。 Christian 建议执行动态作用域变量的一种可能方法诺伊基兴。他在他的Dynamic
类中创建了一个“线程本地哈希”。我对此并不太疯狂。
然后我想起Ruby 1.9有一个tap
方法。我看到很多人使用 tap
在命令链中打印调试值。我认为它可以用来很好地模仿动态范围的变量。
下面是一个想要使用动态作用域变量的情况的示例,以及使用 tap
的解决方案。
如果我有一个博客可以发布此内容并获得一些反馈,我会在那里做。相反,我来 S/O 是为了批评这个想法。发表您的评论,我将给得票最多的人正确答案。
情况
您有一个代表Account
的ActiveRecord 对象,每个帐户has_many
Transaction
。 Transaction
有两个属性:
description
amount
您想要查找 上所有
,请记住,transactions
的总和accountamount
可以是 nil
或 Float
(不,你不能批评它)。
你的第一个想法是:
def account_value
transactions.inject(0){|acum, t| acum += t.amount}
end
当你第一次拥有零金额时,这会爆炸:
TypeError: nil can't be coerced into Fixnum
干净的解决方案
使用tap
临时定义amount = 0
。我们只希望这是临时的,以防万一我们忘记将其设置回来并保存 0 值仍然存在的交易
。
def account_value
transactions.inject(0){|acm, t| t.amount.tap{|amount| amount ||=0; acm+=amount}; acm}
end
由于 amount
的赋值为 0-if-nil 位于 tap
块内,因此我们不必担心忘记将其设置回 无。
你有什么想法?
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.
Situation
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
Clean Solution
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
好吧,我认为您的目标是其他东西,但以下代码修复了您的示例,并且实际上更容易理解:
但我不认为总结金额的方法应该了解
nil
金额,所以(即使你的问题表明我无法与之争论)我会更改amount
方法以返回默认值:尽管如此,我认为你的例子只是太容易解决,而您实际上是在寻找更复杂问题的答案。期待所有其他答案。
Well, I think you're aiming for something else, but the following code fixes your example and is actually easier to understand:
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 theamount
method to return the default instead: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.
我不知道您的解决方案中的动态范围在哪里。
tap
引入了一个新的词法作用域块,值根据词法作用域恢复。顺便说一句,Common Lisp 中的
let
本身也不会创建动态作用域变量。您必须声明
变量special
才能实现这一点(或者如果该变量已经是special
,它将动态地重新定义该变量的值) 。编辑:为了完整起见,我快速实现了一个实现实际动态变量行为的类: http://pastie.org/1700111
其输出是:
编辑 2:这是另一个实现,它无需包装类即可对实例变量执行此操作: http://pastie.org/1706102
I don't see where the dynamic scope in your solution is.
tap
introduces a new lexically scoped block, the values are restored according to the lexical scope.BTW, the
let
in Common Lisp doesn't create dynamically scoped variables by itself, either. You have todeclare
the variablesspecial
to make that happen (or it will redefine the value of a variable dynamically if that variable is alreadyspecial
).EDIT: For the sake of completeness, I quickly implemented a class that implements actual dynamic variable behaviour: http://pastie.org/1700111
The output of that is:
EDIT 2: Here's another implementation that does this for instance variables without the need for a wrapper class: http://pastie.org/1706102
上述问题可以通过使用
||
运算符来解决(如 rubii 所示)。您可以通过调用数组上的
sum
方法来进一步简化这一过程。另一方面,分组计算不应该在 Ruby 中完成。数据库应该承担所有繁重的工作。
The problem stated can be solved by using the
||
operator (as shown by rubii).You can further simplify this by calling the
sum
method on the Array.On the other hand, group calculations should not be done in Ruby. The DB should do all the heavy lifting.