【问题标题】:Changing structure of Ruby proc/block改变 Ruby proc/block 的结构
【发布时间】:2012-04-06 03:07:59
【问题描述】:

我不确定这在 Ruby 中是否可行,但万一有人知道一个好的解决方案。

我想更改块的结构,将其中的特定节点替换为其他代码结构。很像宏。

例如,假设我有一个未评估的代码块

some_method do
  foo
  bar
end

然后我定义 some_method 之类的

def some_method(&block)
   ...
end

在 some_method 中,我真的很想用其他东西替换块中的“bar”,例如与 baz。

我想在不评估块的情况下进行替换,因为最终我将块传递到其他地方。

可行吗?还是没有?

我能想到相当复杂的答案:例如我可以使用定义替换 bar 的附加闭包来传递块,并在评估 bar 时使用 method_missing 和 continuation 将 bar 替换为 baz。但是有没有更简单的方法?

谢谢。

【问题讨论】:

  • 我希望你有一个很好的理由这样做,而不是简单地给 bar 打补丁来做你想做的事。从外部开发人员的角度来看,这种繁重的元编程可能会导致您的应用程序表现得非常不可预测。
  • @tadman:猴子补丁对外部开发人员友好吗?我意识到它超出了规范,但是一旦更改了块,它就与任何其他 ruby​​ 代码块没有什么不同,所以实际上它被很好地抽象了。宏是有用的概念。
  • 您阅读过Macros, Hygiene, and Call By Name in Ruby 并查看过Reg 的rewriteick gems 吗?我认为它们不适用于 1.9,因为依赖于 1.8 ast 解释器中的 sexp/parse-tree。但是一些非常有趣的工作将 ruby​​ 转变为更强大的 lisp 和更强大的函数式语言。

标签: ruby macros metaprogramming


【解决方案1】:

Ruby 没有动态作用域,也没有宏,所以除非你将块包装在一个以bar 作为参数的函数中,并传递该函数,否则我认为你不能替换像这样的代码那。你当然可以使用eval,但我不推荐它=)

【讨论】:

    【解决方案2】:

    这是我能想到的最简单的方法:

    class Base
      def some_method(&block)
         self.instance_eval(&block)
      end
      def foo; puts 'foo'; end
      def bar; puts 'bar'; end
    end
    
    class Replacement < Base
      def foo; puts 'baz'; end
    end
    
    Base.new.some_method do
      foo
      bar
    end
    
    Replacement.new.some_method do
      foo
      bar
    end
    

    输出:

    foo
    bar
    baz
    bar
    

    【讨论】:

    • 这有点用,但是 instance_eval 改变了 self 所以块引用的任何实例变量在写入时都不会在 self 范围内,而是在你所做的 Replacement 实例上。足枪!此外,他不想立即 eval 块,而是将其传递,以便更改在稍后最终执行时生效。
    • 这就是为什么我将Replacement 设为Base 的子类,否则它不会像这样工作。您也可以稍后执行该块,区别在哪里?但我想@Overclocked 要求的几乎是不可能的。
    • 但是子类化只有助于方法调度,ivars 仍然绑定到特定实例
    • 现在我明白你的意思了。好吧,您也可以直接在实例上定义替换方法并通过 self.ivar 调用 ivars ......无论如何,这整个事情似乎是个坏主意 :)
    【解决方案3】:

    别名和 Procs 有帮助吗?

    def foo; p 'foo'; end
    def bar; p 'bar'; end
    def sm(&block)
        @@Block = Proc.new {
            alias :oldbar :bar
            def bar; p 'baz'; end  #redefine
            block.call
            alias :bar :oldbar  #restore
        }
        yield    #prints foo,bar
    end
    
    
    sm do 
        foo 
        bar
    end
    
    
    def later(&block)
        yield
    end
    def delayedEx
        later { @@Block.call}
    end
    
    delayedEx  #prints foo,baz
    bar  #prints bar (unchanged)
    

    这将打印“foo bar foo baz bar”,即:bar 在块中执行不同的操作,但在外部保留其原始行为。

    【讨论】:

      【解决方案4】:
      def some_method(a_proc=Proc.new{puts "Bar"}, &block)
        a_proc.call
        yield
      end
      
      p1 = Proc.new{puts "Baz"}
      
      some_method{puts "a block"}
      some_method(p1){puts "a block"}
      

      【讨论】:

      • 这以什么方式解决了 OP?您的 proc 调用对块的绑定或执行没有影响
      猜你喜欢
      • 1970-01-01
      • 2018-12-28
      • 2012-02-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多