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



我正在学习Odin项目,现在我必须为自己编写一个新的#count方法(使用另一个名称),它的行为与Enumerable模块中的正常方法类似。

计数文件显示如下(http://ruby-doc.org/core-2.4.0/Enumerable.html#method-i-count):

计数→int
计数(项)→int
count{|obj|block}→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

当然有可能。你所要做的就是检查是否给出了一个参数,也检查是否给出一个块。

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

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

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

然而,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在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?

感谢您的帮助!就在我来检查是否有答案之前,我想出了以下解决方案。它肯定可以改进,我会尽量缩短一点,但我更喜欢在我提出它时先把它发布在这里,这可能对像我这样的其他新手有帮助。在下面的代码中,我使用了一个#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

我还发现这两个链接很有用:具有可选参数的方法和http://augustl.com/blog/2008/procs_blocks_and_anonymous_functions/(这提醒我,一个方法可以产生一个块,即使它没有被定义为一个参数,比如&block)。我看到Jorg也在第一个链接的讨论中发表了评论。

最新更新