Ruby 1.9 中的动态变量作用域

发布于 2024-10-24 21:59:27 字数 1484 浏览 1 评论 0原文

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

似乎没有直接的内置方法,就像 Lisp 中的 let 一样。 Christian 建议执行动态作用域变量的一种可能方法诺伊基兴。他在他的Dynamic 类中创建了一个“线程本地哈希”。我对此并不太疯狂。

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

下面是一个想要使用动态作用域变量的情况的示例,以及使用 tap 的解决方案。

如果我有一个博客可以发布此内容并获得一些反馈,我会在那里做。相反,我来 S/O 是为了批评这个想法。发表您的评论,我将给得票最多的人正确答案。


情况

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

  • description
  • amount

您想要查找 上所有 transactions 的总和account,请记住,amount 可以是 nilFloat(不,你不能批评它)。

你的第一个想法是:

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 Transactions. 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

感性 2024-10-31 21:59:27

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

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

但我不认为总结金额的方法应该了解 nil 金额,所以(即使你的问题表明我无法与之争论)我会更改 amount 方法以返回默认值:

def amount
  @amount || 0
end

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

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.

柳若烟 2024-10-31 21:59:27

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

顺便说一句,Common Lisp 中的 let 本身也不会创建动态作用域变量。您必须声明变量special才能实现这一点(或者如果该变量已经是special,它将动态地重新定义该变量的值) 。

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

其输出是:

foo
bar
foo

编辑 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 to declare the variables special to make that happen (or it will redefine the value of a variable dynamically if that variable is already special).

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:

foo
bar
foo

EDIT 2: Here's another implementation that does this for instance variables without the need for a wrapper class: http://pastie.org/1706102

一笔一画续写前缘 2024-10-31 21:59:27

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

您可以通过调用数组上的 sum 方法来进一步简化这一过程。

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

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

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

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.

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

On the other hand, group calculations should not be done in Ruby. The DB should do all the heavy lifting.

account.transactions.sum(:amount) # SELECT SUM(amount)
                                  # FROM   transactions
                                  # WHERE  account_id = account.id
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文