简体   繁体   English

Ruby方法可以接受块或参数吗?

[英]Can a Ruby method accept either a block OR an argument?

I'm doing the lessons on The Odin Project and now I have to write myself a new #count method (with another name) that behaves like the normal one from the Enumerable module. 我正在Odin项目中上课,现在我必须自己编写一个新的#count方法(带有另一个名字),其行为类似于Enumerable模块中的正常方法。

The documentation on count says the following ( http://ruby-doc.org/core-2.4.0/Enumerable.html#method-i-count ): 有关计数的文档说明如下( http://ruby-doc.org/core-2.4.0/Enumerable.html#method-i-count ):

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

Returns the number of items in enum through enumeration. 通过枚举返回enum的项目数。 If an argument is given, the number of items in enum that are equal to item are counted. 如果给出了参数,则enum中与item相等的item数。 If a block is given, it counts the number of elements yielding a true value. 如果给出了一个块,它会计算产生真值的元素数量。

I think I can write all of these as separate methods, but I was mostly wondering if one method definition can combine the last two uses of count - with item and with the block. 我想我可以将所有这些作为单独的方法编写,但我主要想知道一个方法定义是否可以结合count的最后两次使用 - 使用item和block。 Naturally, I'm wondering if all three can be combined in one definition, but I'm mostly interested in the last two. 当然,我想知道这三个是否可以合并在一个定义中,但我最感兴趣的是最后两个。 So far I can't seem to find a possible answer. 到目前为止,我似乎找不到可能的答案。

The documentation page has these examples: 文档页面包含以下示例:

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

Sure it's possible. 当然有可能。 All you have to do is check if an argument is given and also check if a block is given. 您所要做的就是检查是否给出了参数,并检查是否给出了一个块。

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

Or: 要么:

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

The latter is useful because it converts the block to a Proc (named block ) that you can then reuse, as below. 后者很有用,因为它将块转换为可以重复使用的Proc(命名block ),如下所示。

You could implement your own count method like this: 您可以像这样实现自己的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)

See it on repl.it: https://repl.it/@jrunning/YellowishPricklyPenguin-1 在repl.it上看到它: https ://repl.it/@jrunning/YellowishPricklyPenguin-1

Yes, it is possible to do this by making the parameters optional (blocks are always optional anyway) and checking whether a positional argument or a block argument was passed. 是的,可以通过使参数可选(块总是可选的)并检查是否传递位置参数或块参数来实现。

This is a bit messy, though. 不过,这有点乱。 Most Ruby implementations get around this, by implementing the methods in question with privileged access to the private internals of the implementation, which makes it much easier to check whether arguments were passed or not. 大多数Ruby实现通过实现具有对实现的私有内部的特权访问的相关方法来解决这个问题,这使得检查参数是否被传递变得更加容易。 Eg both JRuby and IronRuby have ways to bind multiple overloaded Java / CLI methods to a single Ruby method based on the number and the types of arguments, which makes it possible to implement those three "modes" of count as three simple overloads of the same method. 例如,JRuby和IronRuby都有办法根据参数的数量和类型将多个重载的 Java / CLI方法绑定到单个 Ruby方法,从而可以将这三个“ count模式”实现为三个简单的重载方法。 Here's the example of count from IronRuby , and this is count from JRuby . 这里有一个例子count从IronRuby的 ,这是count从JRuby的

Ruby, however, doesn't support overloading, so you have to implement it manually, which can be a bit awkward. 但是,Ruby不支持重载,所以你必须手动实现它,这可能有点尴尬。 Something like this: 像这样的东西:

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

As you can see, it is a bit awkward. 如你所见,它有点尴尬。 Why don't I simply use nil as a default argument for the optional item parameter? 我为什么不干脆用nil作为可选默认参数item参数? Well, because nil is a valid argument, and I wouldn't be able to distinguish between someone passing no argument and someone passing nil as an argument. 好吧,因为nil是一个有效的参数,我无法区分没有参数的人和传递nil作为参数的人。

For comparison, here is how count is implemented in Rubinius : 为了比较,这里是如何在Rubinius中实现count

 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 

Where I (ab)use the fact that the default argument for an optional parameter is an arbitrary Ruby expression with side-effects such as setting variables, Rubinius uses a special undefined object that is provided by the Rubinius runtime and is equal? 在我(ab)使用可选参数的默认参数是具有副作用(如设置变量)的任意Ruby表达式的事实中,Rubinius使用由Rubinius运行时提供的特殊undefined对象并且是equal? only to itself. 只对自己。

Thank you for your help! 谢谢您的帮助! Just before I came to check if there are any answers I came up with the following solution. 就在我来检查是否有任何答案之前,我提出了以下解决方案。 It can be definitely improved, and I'll try to shorten it a bit, but I prefer to first post it here as I came up with it, it might be helpful for other newbies like me. 它可以肯定地改进,我会尝试缩短它,但我更喜欢先在这里发布它,因为我提出它,它可能对像我这样的其他新手有帮助。 In the code below I'm using a #my_each method that I that works the same as the normal #each. 在下面的代码中,我使用的#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

I also found these two links helpful: A method with an optional parameter and http://augustl.com/blog/2008/procs_blocks_and_anonymous_functions/ (which reminded me that a method can yield a block even if it's not defined as an argument such as &block). 我还发现这两个链接很有用: 带有可选参数的方法http://augustl.com/blog/2008/procs_blocks_and_anonymous_functions/ (这提醒我一个方法可以产生一个块,即使它没有被定义为参数,例如&块)。 I saw Jorg has commented in the first link's discussion, too. 我看到Jorg也在第一个链接的讨论中发表了评论。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM