Ruby 1.9中的动态变量范围

时间:2011-03-22 00:35:54

标签: ruby-on-rails ruby lisp scope tap

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

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

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

下面是一个例子,其中一个人想要使用动态范围变量,以及一个使用tap的解决方案。

如果我有一个博客发布这个,并得到一些反馈,我会在那里做。相反,我来S / O批评这个想法。发表你的批评,我会给出最赞成的人的正确答案。


情况

您有一个ActiveRecord对象代表Account,每个帐户has_many TransactionTransaction有两个属性:

  • description
  • amount

您希望找到transactions上所有account的总和,请注意amount可以是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。我们只希望这是暂时的,以防我们忘记将其设置回来并保存transaction并保留0值。

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

由于amount的{​​0}分配为零,因此我们不必担心忘记将其设置回tap

你有什么想法?

3 个答案:

答案 0 :(得分:6)

好吧,我认为你的目标是其他东西,但是下面的代码修复了你的例子,实际上更容易理解:

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

但我不认为汇总金额的方法应该知道nil金额的默认值,所以(即使你的问题说明我不能与它争论)我会更改amount方法以返回默认值:

def amount
  @amount || 0
end

尽管如此,我认为你的例子太容易解决,而你实际上的目标是找到更复杂问题的答案。期待所有其他答案。

答案 1 :(得分:4)

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

BTW,Common Lisp中的let也不会自己创建动态范围的变量。您必须declare变量special才能实现这一目标(如果该变量已经special,它将动态重新定义变量的值。)

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

输出是:

foo
bar
foo

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

答案 2 :(得分:2)

使用||运算符(如rubii所示)可以解决所述问题。

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

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

另一方面,组计算不应该在Ruby中完成。 DB应该做所有繁重的工作。

account.transactions.sum(:amount) # SELECT SUM(amount)
                                  # FROM   transactions
                                  # WHERE  account_id = account.id