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
The problem you have is that you indirectly test for
@values
type, using an instance variable that happens to be the genericT
. The compiler ain't that smart, and there is a possibility, at runtime, that@type
changes and no longer reflects@values
type. Even if you know it won't, Crystal wants to be safe (otherwise the program segfaults). Crystal complains because you assume a type that it won't acknowledge.Drop
@type
. It's not helping and makes things more complicated than they have to. You already use a generic, now you should ask@values
for what it is, then act accordingly. Crystal will like that, because you ascertain that type will be this and nothing else.That being said, maybe you should have 2 different types to denote
Array
vsArray(Array)
. Maybe your code would be easier to deal with?