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

229 views Asked by At

The issue happens when the variable, that the array was built from, was a nil initially.

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

When v stops being nil on a condition, that is potentially computational and not solvable while compiling:

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

The array gets more complex type, that isn't compatible with current sum implementation, so p z.sum causes compile time error:

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

How am I supposed to fight this properly?
Or maybe it waits for some better implementation of stdlib sum method or anything else?

UPD: inject gives the same:

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

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

There are 2 answers

0
Brian J Cardiff On BEST ANSWER

You can use Iterator#compact_map to select non-nil values. The compiler will be able to infer a Array(Int32) in that case.

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]

Also, notice that typeof(Expr) and Expr.class might lead to different results. The first is the compile time type and the later is the runtime type.

0
asterite On

An alternative solution to what Brian says is to use sum with a block:

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