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

Mis*_*hko 5 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
Run Code Online (Sandbox Code Playgroud)

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

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

jor*_*inl 16

你有apidock的答案:

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

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


Jör*_*tag 6

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

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

reduce :: [a] ? (a ? a ? a) ? a
Run Code Online (Sandbox Code Playgroud)

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

Enumerable[A]#inject {|A, A| A } ? A
Run Code Online (Sandbox Code Playgroud)

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

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

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

fold :: [b] ? a ? (a ? b ? a) ? a
Run Code Online (Sandbox Code Playgroud)

要么

Enumerable[B]#inject(A) {|A, B| A } ? A
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

acc[key] = value
Run Code Online (Sandbox Code Playgroud)

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

acc.tap { acc[key] = value }
Run Code Online (Sandbox Code Playgroud)

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

ary.reduce(0){|acc, e| acc + e.length }
Run Code Online (Sandbox Code Playgroud)

首先转换为整数

ary.map(&:length).reduce(:+)
Run Code Online (Sandbox Code Playgroud)