Make Class member attribute of type Array(T) accept 2D arrays of T

378 views Asked by At

I've defined a Container class. In the @values attribute, I need to store an array, or a 2D array, and elements in those arrays can be Int32 or Float64. If I initialize it like this:

class Container

  def initialize(value) 
    @values = values
  end

end

I get an error: @values : Type, is inferred from assignments to it across the whole program.

If I define it like this:

class Container

  def initialize(value : Array) 
    @values = values
  end

end

I get: can't use Array(T) as the type of instance variable @values of Container(T), use a more specific type

How can I make this class more flexible so I can do:

Container.new([1,2,3])
Container.new([1.0, 3.0, 4.0])
Container.new([[1, 2], [4,3,2],[1]])
Container.new([[1.0, 4.5], [2.2, 0.0]])
2

There are 2 answers

2
Alexander Lozada On BEST ANSWER

After doing some digging, there does seem to be an official way of doing this. However, it must be planned because using that syntax in the constructor gives me the following as of Crystal 0.20.1

def initialize(value : Array(Array | Int32 | Float64))
    @values = value
end

Error in line 3: can't use Array(T) in unions yet, use a more specific type

If I am understanding correctly from your sample data, it seems like types will be homogeneous (i.e. arrays will always contain one specific type). If this is the case, you can simply overload the constructor. It's not a pretty solution, but perhaps it can tie you over.

class Container

  def initialize(value : Array(Array)) 
    @values = value
    calculate
  end

  def initialize(value : Array(Int32))
    @values = value
    calculate
  end

  def initialize(value : Array(Array(Int32)))
    @values = value
    calculate
  end

  def initialize(value : Array(Array(Float64)))
    @values = value
    calculate
  end

  def initialize(value : Array(Float64))
    @values = value
    calculate
  end

  def calculate
    # do stuff here
  end

end

Container.new([1,2,3])
Container.new([1.0, 3.0, 4.0])
Container.new([[1, 2], [4,3,2],[1]])
Container.new([[1.0, 4.5], [2.2, 0.0]])

Edit:

It seems you can use @faaq's solution without specifying the type thanks to the comment by @Sija. They also shared this sample code, which I think is much cleaner than overloading the constructor.

2
faaq On

Why don't use Generics?

class Container(Type)

  def initialize(@values : Type) 
    pp @values
    pp typeof(@values)
  end

end

value = [1,2,3]
Container(typeof(value)).new(value)
value = [1.0, 3.0, 4.0]
Container(typeof(value)).new(value)
value = [[1, 2], [4,3,2],[1]]
Container(typeof(value)).new(value)
value = [[1.0, 4.5], [2.2, 0.0]]
Container(typeof(value)).new(value)

The output is:

@values # => [1, 2, 3]
typeof(@values) # => Array(Int32)
@values # => [1.0, 3.0, 4.0]
typeof(@values) # => Array(Float64)
@values # => [[1, 2], [4, 3, 2], [1]]
typeof(@values) # => Array(Array(Int32))
@values # => [[1.0, 4.5], [2.2, 0.0]]
typeof(@values) # => Array(Array(Float64))