現在、次のようなルビーコードがあります。
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
できeval
ます#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
この SO の質問とNiklas B によるこのコードをご覧になることをお勧めします。