Ruby DSL 的块状参数

  • 本文关键字:参数 DSL Ruby ruby dsl
  • 更新时间 :
  • 英文 :


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

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

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

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

这可能吗?

不,除非您在类String添加应用程序逻辑,否则这是不可能的(请不要这样做)。但是这个 - 甚至看起来很不错 - 如果你玩一点instance_eval是可能的:

grades.sum { grade * weight }

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

module Enumerable
def sum(&block)
inject(0) { |acc, x| acc + x.instance_eval(&block) }
end
end

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

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

grades.sum('grade * weight')

然后,可以使用instance_eval在要传递给#sum的值的上下文中eval该代码。实现将如下所示:

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

你可能想看看这个SO问题和Niklas B的这段代码。

最新更新