"undefined method 'zero' for Nil:Class" when #sum the Array without Nils

"undefined method 'zero' for Nil:Class" when #sum the Array without Nils

当构建数组的变量最初是 nil 时会出现此问题。

y = (1..2).map do
  v = nil
  v = 1
  v
end
p y       # => [1, 1]
p y.class # => Array(Int32)
p y.sum   # => 2

v 在某个条件下停止 nil 时,这可能是计算性的,并且在编译时无法解决:

z = (1..2).map do
  v = nil
  v = 1 if true
  v
end
p z       # [1, 1]
p z.class # => Array(Nil | Int32)

数组变得更复杂,与当前 sum 实现不兼容,因此 p z.sum 导致编译时错误:

undefined method 'zero' for Nil:Class (compile-time type is (Nil | Int32):Class)
 def sum(initial = T.zero)
                     ^~~~

我该如何正确应对?
或者它可能等待 stdlib sum 方法或其他任何更好的实现?

UPD:inject 给出相同的:

p z.inject{ |i, j| i + j }

undefined method '+' for Nil (compile-time type is (Nil | Int32))

您可以使用 Iterator#compact_map 到 select 非零值。在这种情况下,编译器将能够推断出 Array(Int32)

http://play.crystal-lang.org/#/r/e85

z = (1..2).map do
  v = nil
  v = 1 if true
  v
end

pp typeof(z) # => Array(Nil | Int32)
pp z # => z = [1, 1]

y = z.compact_map(&.itself)
pp typeof(y) # => Array(Int32)
pp y # => y = [1, 1]

另外,请注意 typeof(Expr)Expr.class 可能会导致不同的结果。第一个是编译时类型,后面是运行时类型。

Brian 所说的另一种解决方案是将 sum 与块一起使用:

http://play.crystal-lang.org/#/r/ein

z = (1..2).map do
  v = nil
  v = 1 if true
  v
end
puts z.sum { |x| x || 0 } #=> 2