【问题标题】:block like parameter for Ruby DSLRuby DSL 的块状参数
【发布时间】:2012-11-04 03:57:59
【问题描述】:

我有一个 ruby​​ 代码,目前看起来像这样:

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

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

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

这可能吗?

【问题讨论】:

    标签: ruby dsl


    【解决方案1】:

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

    grades.sum { grade * weight }
    

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

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

    【讨论】:

    • 我比我的解决方案更喜欢这个。如果大括号代替括号是可以接受的,这读起来会更好,IMO。
    • +1,您的答案也越来越接近我所需要的,以及已经在 SO 上解决的问题,以及到目前为止我没有时间调查的问题。但我的印象是它还有更多内容......我会在搜索后回来。
    • 知道了。正是this SO questionthis tool by Niklas B. 完全解决了这个问题。
    【解决方案2】:

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

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

    grades.sum('grade * weight')
    

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

    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
    

    【讨论】:

      【解决方案3】:

      您可能想看看this SO questionthis code by Niklas B

      【讨论】:

        猜你喜欢
        • 2012-06-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-03-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多