undefined 'ноль' метода для Nil: Класс, когда #sum the Array без 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))

person Nakilon    schedule 08.09.2015    source источник


Ответы (2)


Вы можете использовать Iterator#compact_map для выбора значений, отличных от нуля. В этом случае компилятор сможет вывести 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 могут привести к разным результатам. Первый - это тип времени компиляции, а второй - тип времени выполнения.

person Brian J Cardiff    schedule 08.09.2015

Альтернативное решение тому, что говорит Брайан, - использовать 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
person asterite    schedule 08.09.2015