6

我对在 Ruby 中使用动态(而不是词法)范围变量感兴趣。

似乎没有直接的内置方式,就像let在 Lisp 中一样。Christian Neukirchen提出了一种执行动态范围变量的可能方法。他在Dynamic课堂上创建了一个“线程本地哈希”。我对此并不太疯狂。

然后我记得 Ruby 1.9 有一个tap方法。我看到很多人用tap命令链打印调试值。我认为它可以用来很好地模仿动态范围的变量。

下面是一个需要使用动态范围变量的情况示例,以及使用tap.

如果我有一个博客可以发布这个并获得一些反馈,我会在那里做。相反,我来 S/O 批评这个想法。发表您的评论,我会给点赞最多的那个人提供正确答案。


情况

你有一个 ActiveRecord 对象,它代表一个Account,每个 account has_many Transaction。ATransaction有两个属性:

  • description
  • amount

你想在 上找到所有的总和,transactions记住accountamount可以是nil或者 a Float(不,你不能批评)。

你的第一个想法是:

def account_value
  transactions.inject(0){|acum, t| acum += t.amount}
end

这会在您第一次拥有 nil 数量时爆炸:

TypeError: nil can't be coerced into Fixnum

清洁解决方案

用于tap临时定义amount = 0。我们只希望这是临时的,以防我们忘记将其重新设置并保存transaction0 值仍然存在。

def account_value
  transactions.inject(0){|acm, t| t.amount.tap{|amount| amount ||=0; acm+=amount}; acm}
end

由于赋值为零,如果为零amount是在tap块内,我们不必担心忘记将其设置回nil.

你怎么认为?

4

3 回答 3

6

好吧,我认为您的目标是别的,但以下代码修复了您的示例,实际上更容易理解:

transactions.inject(0) { |acum, t| acum += t.amount || 0 }

但我不认为总结金额的方法应该知道金额的默认值nil,所以(即使你的问题表明我无法反驳)我会改变amount方法以返回默认值:

def amount
  @amount || 0
end

不过,我认为您的示例太容易解决了,而您实际上是在寻找更复杂问题的答案。期待所有其他答案。

于 2011-03-22T00:55:23.777 回答
4

我看不到您的解决方案中的动态范围在哪里。tap引入了一个新的词法范围块,值根据词法范围恢复。

顺便说一句,letCommon Lisp 本身也不会创建动态范围的变量。您必须declare使用变量special来实现这一点(或者如果该变量已经是,它将动态重新定义变量的值special)。

编辑:为了完整起见,我快速实现了一个实现实际动态变量行为的类:http: //pastie.org/1700111

它的输出是:

foo
bar
foo

编辑 2:这是另一个不需要包装类的实例变量的实现:http: //pastie.org/1706102

于 2011-03-22T11:37:03.647 回答
2

所陈述的问题可以通过使用||操作符来解决(如rubii所示)。

sum您可以通过调用Array 上的方法来进一步简化此操作。

account.transactions.all.sum {|t| t.amount|| 0 }

另一方面,组计算不应该在 Ruby 中进行。数据库应该完成所有繁重的工作。

account.transactions.sum(:amount) # SELECT SUM(amount)
                                  # FROM   transactions
                                  # WHERE  account_id = account.id
于 2011-03-22T04:52:40.543 回答