【问题标题】:Passing a method as a parameter in Ruby在 Ruby 中将方法作为参数传递
【发布时间】:2010-10-06 01:58:00
【问题描述】:

我正在尝试对 Ruby 进行一些处理。因此,我尝试实现“编程集体智能”Ruby 一书中的算法(用 Python 给出)。

在第 8 章中,作者传递了一个方法 a 作为参数。这似乎在 Python 中有效,但在 Ruby 中无效。

我这里有方法

def gaussian(dist, sigma=10.0)
  foo
end

并想用另一种方法调用它

def weightedknn(data, vec1, k = 5, weightf = gaussian)
  foo
  weight = weightf(dist)
  foo
end

我得到的只是一个错误

ArgumentError: wrong number of arguments (0 for 1)

【问题讨论】:

    标签: ruby parameters methods


    【解决方案1】:

    引用块和 Procs 的 cmets 是正确的,因为它们在 Ruby 中更常见。但是如果你愿意,你可以传递一个方法。你调用method 获取方法,调用.call 调用它:

    def weightedknn( data, vec1, k = 5, weightf = method(:gaussian) )
      ...
      weight = weightf.call( dist )
      ...
    end
    

    【讨论】:

    • 这很有趣。值得注意的是,在将方法名称转换为可调用符号时,您只调用了一次 method( :<name> )。您可以将该结果存储在变量或参数中,然后像任何其他变量一样继续将其传递给子函数...
    • 或者,您可以在调用方法时使用它,而不是在参数列表中使用 method 语法: weightedknn( data, vec1, k, method( :gaussian) )
    • 此方法比使用 proc 或块更好,因为您不必处理参数 - 它只适用于方法想要的任何内容。
    • 为了完成,如果你想传递在其他地方定义的方法,请执行SomewhereElse.method(:method_name)。太酷了!
    • 这可能是它自己的问题,但是,我如何确定符号是否引用了函数或其他内容?我试过:func.class,但这只是symbol
    【解决方案2】:

    你想要一个 proc 对象:

    gaussian = Proc.new do |dist, *args|
      sigma = args.first || 10.0
      ...
    end
    
    def weightedknn(data, vec1, k = 5, weightf = gaussian)
      ...
      weight = weightf.call(dist)
      ...
    end
    

    请注意,您不能在这样的块声明中设置默认参数。因此,您需要使用 splat 并在 proc 代码本身中设置默认值。


    或者,根据您的所有范围,传入方法名称可能更容易。

    def weightedknn(data, vec1, k = 5, weightf = :gaussian)
      ...
      weight = self.send(weightf)
      ...
    end
    

    在这种情况下,您只是调用在对象上定义的方法,而不是传入完整的代码块。根据您的结构,您可能需要将 self.send 替换为 object_that_has_the_these_math_methods.send


    最后但并非最不重要的一点是,您可以在方法上挂起一个块。

    def weightedknn(data, vec1, k = 5)
      ...
      weight = 
        if block_given?
          yield(dist)
        else
          gaussian.call(dist)
        end
      end
      ...
    end
    
    weightedknn(foo, bar) do |dist|
      # square the dist
      dist * dist
    end
    

    但听起来您希望这里有更多可重用的代码块。

    【讨论】:

    • 我认为第二个选项是最好的选择(即使用 Object.send()),缺点是你需要为所有它使用一个类(这是你应该做的无论如何在 OO 中:))。它比一直传递一个块(Proc)更干,你甚至可以通过包装方法传递参数。
    • 另外,如果你想用send做foo.bar(a,b),那就是foo.send(:bar, a, b)。 splat (*) 运算符允许您执行 foo.send(:bar, *[a,b]) 如果您发现您想要一个任意长度的参数数组 - 假设 bar 方法可以吸收它们
    【解决方案3】:

    您可以使用method(:function) 方式将方法作为参数传递。下面是一个非常简单的例子:

    定义双(一) 返回一个 * 2 结尾 => 无 def method_with_function_as_param(回调,数字) callback.call(号码) 结尾 => 无 method_with_function_as_param(方法(:双),10) => 20

    【讨论】:

    • 我遇到了一个范围更复杂的方法的问题,终于想出了怎么做,希望这可以帮助某人:如果你的方法在另一个类中,你应该调用最后一个像method_with_function_as_param(Class.method(:method_name),...) 这样的代码行而不是method(:Class.method_name)
    • 感谢您的回答,我发现了名为method的方法。度过了我的一天,但我想这就是我更喜欢函数式语言的原因,不需要为了得到你想要的东西而做这样的杂技。不管怎样,我挖红宝石
    【解决方案4】:

    正常的 Ruby 方法是使用块。

    所以它会是这样的:

    def weightedknn( data, vec1, k = 5 )
      foo
      weight = yield( dist )
      foo
    end
    

    并像这样使用:

    weightenknn( data, vec1 ) { |dist| gaussian( dist ) }
    

    这种模式在 Ruby 中被广泛使用。

    【讨论】:

      【解决方案5】:

      您可以在方法的Method 实例上使用& 运算符将方法转换为块

      例子:

      def foo(arg)
        p arg
      end
      
      def bar(&block)
        p 'bar'
        block.call('foo')
      end
      
      bar(&method(:foo))
      

      更多详情http://weblog.raganwald.com/2008/06/what-does-do-when-used-as-unary.html

      【讨论】:

        【解决方案6】:

        你必须调用函数对象的方法“call”:

        weight = weightf.call( dist )
        

        编辑:如 cmets 中所述,这种方法是错误的。如果您使用 Procs 而不是普通函数,它会起作用。

        【讨论】:

        • 当他在 arg 列表中执行 weightf = gaussian 时,它实际上是在尝试执行 gaussian 并将结果分配为 ​​weightf 的默认值。该调用不需要 args 和崩溃。所以 weightf 甚至还不是一个带有 call 方法的 proc 对象。
        • 这(即做错和解释原因的评论)实际上让我完全理解了接受的答案,所以谢谢! +1
        【解决方案7】:

        我建议使用 & 来访问函数中的命名块。按照this article 中给出的建议,您可以编写类似这样的内容(这是我工作程序中的真正废品):

          # Returns a valid hash for html form select element, combined of all entities
          # for the given +model+, where only id and name attributes are taken as
          # values and keys correspondingly. Provide block returning boolean if you
          # need to select only specific entities.
          #
          # * *Args*    :
          #   - +model+ -> ORM interface for specific entities'
          #   - +&cond+ -> block {|x| boolean}, filtering entities upon iterations
          # * *Returns* :
          #   - hash of {entity.id => entity.name}
          #
          def make_select_list( model, &cond )
            cond ||= proc { true } # cond defaults to proc { true }
            # Entities filtered by cond, followed by filtration by (id, name)
            model.all.map do |x|
              cond.( x ) ? { x.id => x.name } : {}
            end.reduce Hash.new do |memo, e| memo.merge( e ) end
          end
        

        Afterwerds,你可以这样调用这个函数:

        @contests = make_select_list Contest do |contest|
          logged_admin? or contest.organizer == @current_user
        end
        

        如果您不需要过滤您的选择,您只需省略该块:

        @categories = make_select_list( Category ) # selects all categories
        

        Ruby 块的强大功能如此之多。

        【讨论】:

          【解决方案8】:

          Procmethod call 类似,您也可以将lambda 作为weightf 参数传递:

          def main
            gaussian = -> (params) {
              ...
            }
            weightedknn(data, vec1, k = 5, gaussian, params)
            # Use symbol :gaussian if method exists instead
          end
          
          
          def weightedknn(data, vec1, k = 5, weightf, params)
            ...
            weight = weightf.call(params)
            ...
          end
          

          【讨论】:

            【解决方案9】:

            您也可以使用“eval”,并将方法作为字符串参数传递,然后在其他方法中简单地对其进行评估。

            【讨论】:

            • 这是非常糟糕的做法,千万不要这样做!
            • @Developer 为什么这被认为是不好的做法?
            • 在性能原因中,eval 可以执行任意代码,因此极易受到各种攻击。
            猜你喜欢
            • 2014-04-09
            • 2013-08-10
            • 2016-11-02
            • 2015-05-01
            • 1970-01-01
            • 1970-01-01
            • 2021-07-19
            • 2010-10-01
            • 1970-01-01
            相关资源
            最近更新 更多