在Ruby中重写枚举#inject以将符号作为参数

u7up0aaq  于 2022-11-22  发布在  Ruby
关注(0)|答案(1)|浏览(114)

所以,我正在浏览The Odin Project's Ruby Path,在其中一个项目中,我们必须将一些Enum函数重写为它们的基本功能。问题是,我想尝试超越并重新创建那些函数,尽可能接近原始函数,这就带来了Enum#inject
我使用以下代码重新创建了它(在Enumerable模块中)

def my_inject(initial = first, sym = nil)
  memo = initial

  enum = to_a

  if block_given?
    enum.my_each_with_index do |el, idx|
      next if memo == first && idx.zero?

      memo = yield memo, el
    end
  else
    block = sym.to_proc
    enum.my_each_with_index do |el, idx|
      next if memo == first && idx.zero?

      memo = block.call memo, el
    end
  end
  memo
end

my_each_with_indexeach_with_index的自定义版本,其工作方式应该如此)
这个版本 * 几乎 * 工作正常。问题是只有当我只用Symbol作为参数调用它时,就像在((5..10).my_inject(:+))中一样,导致它抛出'my_inject': undefined method 'to_proc' for nil:NilClass (NoMethodError)
我猜这是因为符号作为initial值传递,作为函数的第一个参数。
我想尝试写一堆检查(比如检查函数是否只有一个参数,并且该参数是一个符号),但我想知道是否有更简单、更干净的方法来完成。
(请记住,我学习代码的时间不超过6个月,所以我是一个非常非常非常的绿色)。
谢谢你的帮助!

osh3o9ms

osh3o9ms1#

内置 的 inject 是 非常 多态 的 , 所以 在 尝试 从头 开始 实现 它 之前 ( 不 看 源 代码 ) , 我们 需要 研究 它 在 不同 情况 下 的 行为 。 我 跳过 了 你 已经 知道 的 事情 ( 比如 如果 没有 显 式 提供 , 使用 第 一 个 元素 作为 初始 值 等 ) , 除 此 之外 :

[1,2,3].inject(0, :+, :foo) #=> ArgumentError: wrong number of arguments (given 3, expected 0..2)
# Max. arity is strict

[1,2,3].inject(0, :+) { 1 } #=> 6
# If both symbol _and_ block are provided, the former dominates

[1,2,3].inject(:+) { |acc, x| acc } #=> :+
# With only 1 parameter and a block the former will be treated as an init value, not a proc.

[1,2,3].inject("+") #=> 6
[1,2,3].inject("+") { |acc, x| acc } #=> "+"
# Strings work too. This is important, because strings _don't respond to `to_proc`_, so we would need smth. else

[1,2,3].inject #=> LocalJumpError: no block given
# Ok, local jump error means that we try to yield in the cases where the 2nd parameter is not provided

[1,2,3].inject(nil) #=> TypeError: nil is not a symbol nor a string
# But if it is provided, we try to do with it something that quacks pretty much like `send`...

中 的 每 一 个
记住 这些 信息 后 , 我们 可以 编写 如下 代码 :

module Enum
  # Just for convenience, you don't need it if you implement your own `each`
  include Enumerable

  def my_inject(acc = nil, sym = nil)
    # With a single attribute and no block we assume that the init value is in fact nil
    # and what is provided should be "called" on elements somehow
    if acc && !sym && !block_given?
      sym, acc = acc, nil
    end
  
    each do |element|
      if !acc
        # If we are not initialized yet, we just assign an element to our accumulator
        # and proceed
        acc = element
      elsif sym
        # If the "symbol" was provided explicitly (or resolved as such in a single parameter case)
        # we try to call the appropriate method on the accumulator. 
        acc = acc.send(sym, element)
      else
        # Otherwise just try to yield
        acc = yield acc, element
      end
    end
    
    acc
  end
end

格式
忍耐 一下 , 我们 就 快 到 了 : ) 让 我们 来 看看 它 是 如何 嘎嘎 叫 的 :

class Ary < Array
  include Enum
end

ary = Ary.new.push(1,2,3)

ary.my_inject #=> LocalJumpError: no block given
ary.my_inject(0) #=> TypeError: 0 is not a symbol nor a string
ary.my_inject("+") #=> 6
ary.my_inject(:+) #=> 6
ary.my_inject(0, :+) #=> 6
ary.my_inject(1, :+) #=> 7
ary.my_inject(1, :+) { 1 } #=> 7
ary.my_inject(:+) { |acc, x| acc } #=> :+

格式
所以 , 几乎 是 一样 的 。 可能 还有 一些 其他 的 边缘 情况 , 我 的 实现 不能 满足 , 但 我 把 它们 留给 你 : )

相关问题