2012-11-03 71 views
1

我有一個目前看起來像這樣的Ruby代碼:塊像參數爲Ruby DSL

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

對於用戶保持DSL,我想實現這樣的:

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

是這可能嗎?

回答

6

不,這是不可能的,除非你修改類String添加你的應用程序邏輯(請不要這樣做)。但這哪位甚至看起來beter-是可能的,如果你玩一點點與instance_eval

grades.sum { grade * weight } 

例如,假設grades是一個數組:

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

我比我的解決方案更喜歡這個。如果大括號可以接受而不是括號,這會更好地讀取IMO。 –

+0

+1,你的答案越來越接近我需要的東西,已經解決了什麼問題,以及我到現在爲止沒有時間去調查。但我有一種印象,那就是有更多的東西......我會在搜索後回來。 –

+0

明白了。這是[這個問題](http://stackoverflow.com/questions/10058996)和[Niklas B的這個工具](https://github.com/niklasb/ruby-dynamic-binding),完全處理這個問題。 –

1

嚴格地說,沒有,因爲在將它們作爲參數傳遞給「sum」之前,Ruby解釋器會嘗試將字符串「grade」和「weight」相乘。你可以通過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