1

我有一个红宝石代码,目前看起来像这样:

grades.sum{|g| g.grade * g.weight}

对于用户维护的 DSL,我想实现这样的东西:

grades.sum('grade' * 'weight')

这可能吗?

4

3 回答 3

6

不,这是不可能的,除非您对类进行猴子补丁String以添加您的应用程序逻辑(请不要这样做)。但是,如果您稍微玩一下,这 - 甚至看起来更好 - 是可能的instance_eval

grades.sum { grade * weight }

例如,假设grades是一个数组:

module Enumerable
  def sum(&block)
    inject(0) { |acc, x| acc + x.instance_eval(&block) }
  end
end
于 2012-11-03T15:28:04.453 回答
1

严格来说,不,因为 Ruby 解释器会尝试将字符串“grade”和“weight”相乘,然后将它们作为参数传递给“sum”。您可以通过monkeypatching String 来覆盖您想要允许的所有操作来实现它,但这是一个糟糕的想法,将成为维护的噩梦。

现在,也就是说,您有几种方法可以实现这一目标。如果您需要一个完整的字符串作为参数:

grades.sum('grade * weight')

然后,您可以在传递给的值的上下文中使用instance_evaleval代码#sum。实现看起来像:

class Grade
  def weight
    0.5
  end

  def grade
    75
  end
end

class Array
  def sum(code = nil)
    if block_given?
      inject(0) {|s, v| s + yield(v) }
    else
      sum {|v| v.instance_eval code }
    end
  end
end

grades = Array.new(5, Grade.new)
puts grades.sum("weight * grade")

# Expected output: 5 * 75 * 0.5 = 187.5
#
# Actual output:
# % ruby grades.rb
# 187.5

这也保留了您的原始块形式:

puts grades.sum {|g| g.weight * g.grade } # => 187.5
于 2012-11-03T15:28:42.153 回答
0

您可能想看看这个 SO questionNiklas B 的这段代码

于 2012-11-03T18:13:55.327 回答