为什么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通常会咬你
acc[key] = value
在您的块中,因为赋值评估为指定的值,而不是赋值的接收者。 你必须用它替换它
acc.tap { acc[key] = value }
在您的特定情况下,已经提到了两种解决方案。 使用显式累加器
ary.reduce(0){|acc, e| acc + e.length }
或首先转换为整数
ary.map(&:length).reduce(:+)
如果没有初始值,则inject
将集合中的第一个元素用作初始值。
见ruby-doc 。