【问题标题】:Pass block into define_method将块传递给define_method
【发布时间】:2015-01-10 03:08:38
【问题描述】:

问题

我发现自己经常使用一种模式,所以我想把它擦干。我有这样的东西:

class InfoGatherer
  def foo
    true
  end

  def people
    unless @people
      @people = # Long and complex calculation (using foo)
    end
    @people
  end
end

我想把它晾干,看起来像这样:

class InfoGatherer
  extend AttrCalculator

  def foo
    true
  end

  attr_calculator(:people) { # Long and complex calculation (using foo) }
end

为了实现这一点,我定义了一个模块 AttrCalculator 以扩展为 InfoGatherer。这是我尝试过的:

module AttrCalculator
  def attr_calculator(variable_name_symbol)
    variable_name = "@#{variable_name_symbol}"

    define_method variable_name_symbol do
      unless instance_variable_defined?(variable_name)
        instance_variable_set(variable_name, block.call)
      end
      instance_variable_get(variable_name)
    end

  end
end

不幸的是,当我尝试像InfoGatherer.new.people 这样简单的事情时,我得到:

NameError: undefined local variable or method `foo' for InfoGatherer:Class

嗯,这很奇怪。为什么block运行在InfoGatherer:Class的范围内,而不是它的实例InfoGatherer.new

研究

我知道我不能使用yield,因为那会尝试捕获错误的块,如here 所示。 我尝试使用self.instance_exec(block) 代替上面的block.call,但随后收到一个新错误:

LocalJumpError: no block given

嗯?我在this SO question 中看到了同样的错误,但我已经在使用括号表示法,所以那里的答案似乎不适用。

我也尝试使用class_eval,但我不确定如何在字符串中调用block。这肯定行不通:

class_eval("
  def #{variable_name_symbol}
    unless #{variable_name}
      #{variable_name} = #{block.call}
    end
    #{variable_name}
  end
")

【问题讨论】:

    标签: ruby memoization


    【解决方案1】:

    该用例称为记忆化。它可以很容易地做到:

    def people
      @people ||= # Long and complex calculation (using foo)
    end
    

    你不应该像现在这样陷入混乱。

    【讨论】:

    • 当它是一个简短的计算时,我就是这样做的。 :) 但你是对的;任何复杂的计算都可以移到辅助方法中,并轻松放在一行上。不过,我仍然很好奇将块传递给define_method 的正确方法是什么。即使我提供的用例不是一个可靠的用例。
    • 它不必在一行中。如果你的代码很长,可以||= begin ... end
    【解决方案2】:

    问题在于,在define_method 内部,self 令人惊讶的是InfoGatherer,而不是InfoGatherer实例。所以我和self.instance_exec(block)走上了正轨。

    有效的解决方案是self.instance_exec(&block)(注意与号)。我猜解释器不承认 block 是一个块,除非你这样标记它?如果有人能比我更好地解释这一点,请这样做。

    附带说明,这不是解决这个特定问题的最佳方法。请参阅@sawa 的答案,了解一种记忆复杂计算的简洁方法。

    【讨论】:

      【解决方案3】:

      扩展最后的人

      def people(varariable = nil)
        @people ||= ComplexCalculation.new(variable).evaluate
      end
      
      class ComplexCalculation
      
        def initialize(variable)
          @variable = variable
        end
      
        def evaluate(variable)
          #stuff
        end
      
      end
      

      通过提取此类,您可以隔离这种复杂性并获得更好的体验。

      【讨论】:

      • 把它作为一个选项指出来是件好事,但这只是其中之一,包括@people ||= my_method(variable)。顺便说一句,我想你的意思是def people(variable=nil)
      猜你喜欢
      • 2010-09-10
      • 2013-08-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-19
      • 2017-12-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多