我对在Ruby中使用动态(而不是词法)范围变量感兴趣。
似乎没有直接的内置方式,就像Lisp中的let
一样。 Christian Neukirchen建议了一种可能的方法来执行动态范围变量。他在Dynamic
课程中创建了“线程局部哈希”。我对此并不太疯狂。
然后我记得Ruby 1.9有一个tap
方法。我看到很多人使用tap
在一系列命令中打印调试值。我认为它可以用来非常好地模仿动态范围的变量。
下面是一个例子,其中一个人想要使用动态范围变量,以及一个使用tap
的解决方案。
如果我有一个博客发布这个,并得到一些反馈,我会在那里做。相反,我来S / O批评这个想法。发表你的批评,我会给出最赞成的人的正确答案。
您有一个ActiveRecord对象代表Account
,每个帐户has_many
Transaction
。 Transaction
有两个属性:
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
。
你有什么想法?
答案 0 :(得分:6)
好吧,我认为你的目标是其他东西,但是下面的代码修复了你的例子,实际上更容易理解:
transactions.inject(0) { |acum, t| acum += t.amount || 0 }
但我不认为汇总金额的方法应该知道nil
金额的默认值,所以(即使你的问题说明我不能与它争论)我会更改amount
方法以返回默认值:
def amount
@amount || 0
end
尽管如此,我认为你的例子太容易解决,而你实际上的目标是找到更复杂问题的答案。期待所有其他答案。
答案 1 :(得分:4)
我没有看到解决方案中的动态范围在哪里。 tap
引入了一个新的词法范围块,根据词法范围恢复值。
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