为什么Ruby注入方法不能在没有初始值的情况下总结字符串长度?

时间:2010-11-30 11:02:07

标签: ruby

为什么以下代码会发出错误?

['hello','stack','overflow'].inject{|memo,s|memo+s.length}

TypeError: can't convert Fixnum into String
        from (irb):2:in `+'
        from (irb):2:in `block in irb_binding'
        from (irb):2:in `each'
        from (irb):2:in `inject'
        from (irb):2

如果传递了初始值,则可以正常工作:

['hello','stack','overflow'].inject(0){|memo,s|memo+s.length}
=> 18

3 个答案:

答案 0 :(得分:16)

您可以在apidock中找到答案:

  

如果没有明确指定备忘录的初始值,则使用集合的第一个元素作为备忘录的初始值。

也就是说,如果没有初始值,您就会尝试'hello' + 'stack'.length

答案 1 :(得分:6)

正如错误消息已经告诉您的那样,问题是您有一个TypeError。仅仅因为Ruby被动态地和隐式地输入并不意味着你不必考虑类型。

没有显式累加器的Enumerable#inject类型(通常称为reduce)类似于

reduce :: [a] → (a → a → a) → a

或者用我编写的更多Rubyish符号

Enumerable[A]#inject {|A, A| A } → A

您会注意到所有类型都是相同的。 Enumerable的元素类型,块的两个参数类型,块的返回类型以及整个方法的返回类型。

在您的情况下,块的类型不会加起来。该块传递了两个String,它应该返回String。但是,您在第一个参数(+)上调用String方法,其参数为Integer。但是String#+不接受Integer它只需要String或更精确的东西可以转换String,即某事响应#to_str。这就是为TypeError获得String#+的原因。

带有显式累加器的Enumerable#inject 的类型(通常称为fold)类似于

fold :: [b] → a → (a → b → a) → a

Enumerable[B]#inject(A) {|A, B| A } → A

在这里,您可以看到累加器可以具有不同的类型,而不是集合的元素类型。这正是你所需要的。

这两条规则通常可以解决所有与Enumerable#inject相关的问题:

  1. 累加器的类型和块的返回类型必须相同
  2. 当未传递显式累加器时,累加器的类型与元素类型
  3. 相同

    当您执行

    之类的操作时,规则#1通常会咬你
    acc[key] = value
    
    在您的块中

    ,因为赋值评估为指定的值,而不是赋值的接收者。你必须用

    替换它
    acc.tap { acc[key] = value }
    

    在您的特定情况下,已经提到了两种解决方案。使用显式累加器

    ary.reduce(0){|acc, e| acc + e.length }
    

    或首先转换为整数

    ary.map(&:length).reduce(:+)
    

答案 2 :(得分:3)

没有初始值,inject使用集合中的第一个元素作为初始值。

请参阅ruby-doc