为什么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 

你有apidock的答案:

如果没有为memo显式指定初始值,则使用collection的第一个元素作为memo的初始值。

也就是说,没有初始值,你就试图做'hello' + 'stack'.length

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

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

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

或者用一种更简洁的符号来构建

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

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

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

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

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

要么

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

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

这两条规则通常会Enumerable#inject您完成所有Enumerable#inject related问题:

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

当你做某事时,规则#1通常会咬你

 acc[key] = value 

在您的块中,因为赋值评估为指定的值,而不是赋值的接收者。 你必须用它替换它

 acc.tap { acc[key] = value } 

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

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

首先转换为整数

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

如果没有初始值,则inject将集合中的第一个元素用作初始值。

见ruby-doc 。