【问题标题】:Can a Ruby method accept either a block OR an argument?Ruby 方法可以接受块或参数吗?
【发布时间】:2017-06-26 10:57:44
【问题描述】:

我正在上 Odin 项目的课程,现在我必须为自己编写一个新的 #count 方法(使用另一个名称),其行为类似于 Enumerable 模块中的普通方法。

有关计数的文档说以下 (http://ruby-doc.org/core-2.4.0/Enumerable.html#method-i-count):

count → int
count(item) → int
count { |obj|块 } → int

通过枚举返回enum 中的项目数。如果 给定参数,enum 中等于 item 的项目数 被计算在内。如果给定一个块,它会计算元素的数量 产生一个真实的价值。

我想我可以将所有这些编写为单独的方法,但我主要想知道一个方法定义是否可以结合count 的最后两个用途 - 与item 和块。自然地,我想知道这三个是否可以组合成一个定义,但我最感兴趣的是最后两个。到目前为止,我似乎找不到可能的答案。

文档页面有以下示例:

ary = [1, 2, 4, 2]
ary.count               #=> 4
ary.count(2)            #=> 2
ary.count{ |x| x%2==0 } #=> 3

【问题讨论】:

    标签: ruby count enumerable method-declaration


    【解决方案1】:

    当然可以。您所要做的就是检查是否给出了参数并检查是否给出了块。

    def call_me(arg=nil)
      puts "arg given" unless arg.nil?
      puts "block given" if block_given?
    end
    
    call_me(1)
    # => arg given
    call_me { "foo" }
    # => block given
    call_me(1) { "foo" }
    # => arg given
    #    block given
    

    或者:

    def call_me(arg=nil, &block)
      puts "arg given" unless arg.nil?
      puts "block given" unless block.nil?
    end
    

    后者很有用,因为它将块转换为 Proc(命名为 block),然后您可以重复使用,如下所示。

    您可以像这样实现自己的count 方法:

    module Enumerable
      def my_count(*args, &block)
        return size if args.empty? && block.nil?
        raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 1)" if args.size > 1
    
        counter = block.nil? ? ->(i) { i == args[0] } : block
        sum {|i| counter.call(i) ? 1 : 0 }
      end
    end
    
    arr = [1,2,3,4,5]
    p arr.my_count # => 5
    p arr.my_count(2) # => 1
    p arr.my_count(&:even?) # => 2
    p arr.my_count(2, 3) # => ArgumentError: wrong number of arguments (given 2, expected 1)
    

    在 repl.it 上查看:https://repl.it/@jrunning/YellowishPricklyPenguin-1

    【讨论】:

    • 感谢您的回答!我最终做了类似的事情,并且奏效了。我将在下面发布我的解决方案。不过,这只是一个小问题-在您的第一个示例中,第三行不应该是“如果 block_given 则放置“块给定”?”,如果,而不是除非?我尝试了类似于您上一个示例的方法,但无法弄清楚“block.nil”?部分,我总是以一个参数错误结束。
    • 是的,我的意思是if,而不是unless。接得好。但是,如果没有看到您的代码,我无法解释您的 ArgumentError。
    • 我的意思是参数数量错误的参数错误,因为 &block 被显式声明为参数,我不能在没有块的情况下使用它,因为我没有想到 block.nil?部分。
    【解决方案2】:

    是的,可以通过将参数设为可选(无论如何,块始终是可选的)并检查是否传递了位置参数或块参数来做到这一点。

    不过,这有点混乱。大多数 Ruby 实现解决了这个问题,通过实现有问题的方法来实现对实现的私有内部的特权访问,这使得检查参数是否被传递变得更加容易。例如。 JRuby 和 IronRuby 都可以根据参数的数量和类型将多个重载 Java / CLI 方法绑定到一个单个 Ruby 方法,这使得实现这些方法成为可能count 的三个“模式”作为同一方法的三个简单重载。这是count from IronRuby的例子,这是count from JRuby

    然而,Ruby 不支持重载,因此您必须手动实现它,这可能有点尴尬。像这样的:

    module Enumerable
      def count(item = (item_not_given = true; nil))
        item_given = !item_not_given
        warn 'given block not used' if block_given? && item_given
    
        return count(&item.method(:==)) if item_given
        return inject(0) {|acc, el| if yield el then acc + 1 else acc end } if block_given?
        count(&:itself)
      end
    end
    

    如您所见,这有点尴尬。为什么我不简单地使用nil 作为可选item 参数的默认参数?好吧,因为nil 是一个有效参数,我无法区分没有参数传递的人和将nil 作为参数传递的人。

    为了比较,这里是count is implemented in Rubinius

    def count(item = undefined)
      seq = 0
      if !undefined.equal?(item)
        each do
          element = Rubinius.single_block_arg
          seq += 1 if item == element
        end
      elsif block_given?
        each { |element| seq += 1 if yield(element) }
      else
        each { seq += 1 }
      end
      seq
    end
    

    我(ab)使用的事实是可选参数的默认参数是具有副作用(例如设置变量)的任意 Ruby 表达式,Rubinius 使用由 Rubinius 运行时提供的特殊 undefined 对象,并且是equal? 只对自己。

    【讨论】:

    • 谢谢,我发现你的最后一个例子最容易理解。一段时间后我将不得不再次与其他人联系,但他们似乎也很有帮助。 :)
    【解决方案3】:

    感谢您的帮助!就在我来检查是否有任何答案之前,我想出了以下解决方案。它肯定可以改进,我会尽量缩短一点,但我更喜欢先在这里发布,因为我想出了它,它可能对像我这样的其他新手有帮助。在下面的代码中,我使用了一个 #my_each 方法,它与正常的 #each 工作方式相同。

    def my_count(arg=nil)
        sum = 0
        if block_given? && arg == nil
            self.my_each do |elem|
                if yield(elem)
                    sum += 1
                end
            end
        elsif !block_given? && arg != nil
            self.my_each do |elem|
                if arg == elem
                    sum += 1
                end
            end
        else
            self.my_each do |elem|
                sum += 1
            end
        end
        sum
    end
    

    我还发现这两个链接很有帮助: A method with an optional parameterhttp://augustl.com/blog/2008/procs_blocks_and_anonymous_functions/(它提醒我一个方法可以产生一个块,即使它没有被定义为一个参数,例如&block)。我看到 Jorg 在第一个链接的讨论中也发表了评论。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多