1

I already asked a related question here and the answer did fix my issue but I have a more general misunderstanding of how crystal checks types since I keep running into similar issue so if someone could help me figure this out I'd be greatful. I've tried many things that would work great with Ruby but are absolutely not working with Crystal (I know they have many differences but I'm more familiar with Ruby).

Here is a class:

class Narray(T)
  getter shape
  getter values

  @shape : Tuple(Int32, Int32) | Nil

  def initialize(values : T)
    @values = values
    @type = T
    @shape = set_shape
  end

  def set_shape
    if (@type == Array(Array(Int32))) || (@type == Array(Array(Float64)))

      return {values.size, values[0].size}      # Line causing the error

    elsif (@type == Array(Int32)) || (@type == Array(Float64))
      return {1, @values.size}
    end
  end

  def is_matrix?
    if @values[0].is_a?(Array(Int32)) || @values[0].is_a?(Array(Float64))
      return true
    else
      return false
    end
  end
end

To define an array, I should do:

arr1 = Narray.new([1,2,3])

Which would run through set_shape to retrieve the shape of the array (a tuple representing the number of rows and number of columns). In this method, I check in the if statement if it's a 2D array or not. However, when running the line above, I get this error:

in script.cr:16: undefined method 'size' for Int32

      return {values.size, values[0].size}

Which is exactly what the first if statement should avoid. Since I'm initializing a 1D array, it shouldn't go through the .size part.

I'm pretty sure it's a trivial explanation, and that there is something pretty basic that I didn't get but I'd like to get it instead of stumbling upon this issue all the time.

How can I check for type in Crystal since the way I'm using isn't the right way ? I've tried using is_a?, == Type, using the is_matrix? method (which works fine and does the job of determining if it's 2D or 1D but still runs through the wrong part).

EDIT: according to the first answer, I've changed the set_shape method:

  def set_shape
    if (@values.is_a? Array(Array(Int32))) || (@values.is_a? Array(Array(Float64)))
      return {values.size, values[0].size}
    elsif (@values.is_a? Array(Int32)) || (@values.is_a? Array(Float64))
      return {1, @values.size}
    end
  end

But I still have the exact same error

4

2 回答 2

3

您遇到的问题是您@values使用恰好是通用的实例变量间接测试类型T。编译器不是那么聪明,并且在运行时有可能@type发生变化并且不再反映@values类型。即使您知道它不会,Crystal 也希望是安全的(否则程序会出现段错误)。Crystal 抱怨是因为您假设了一种它不会承认的类型。

放下@type。这无济于事,并且使事情变得比他们必须做的更复杂。您已经使用了泛型,现在您应该询问@values它是什么,然后采取相应的行动。Crystal 会喜欢这样,因为您确定该类型将是 this 而不是别的。

话虽这么说,也许你应该有两种不同的类型来表示Arrayvs Array(Array)。也许您的代码会更容易处理?

于 2016-12-25T20:05:55.543 回答
1

以下代码有效:

class Narray(T)
  getter shape
  getter values

  @shape : Tuple(Int32, Int32) | Nil

  def initialize(@values : T)
    @shape = set_shape
  end

  def set_shape
    values = @values # => asign to local var
    if values.is_a?(Array(Array(Int32))) || values.is_a?(Array(Array(Float64)))
      return {values.size, values[0].size}
    elsif (T == Array(Int32)) || (T == Array(Float64))
      return {1, values.size}
    end
  end

  def is_matrix?
    if @values[0].is_a?(Array(Int32)) || @values[0].is_a?(Array(Float64))
      return true
    else
      return false
    end
  end
end

arr1 = Narray.new([[1, 2], [3, 4]])
pp arr1.set_shape # => {2,2}
pp arr1.is_matrix? # => true
于 2016-12-26T20:18:35.340 回答