Search code examples
crystal-lang

Crystal how to convert Array(T) to Array(T?)


I'm making Serie - an immutable time-series with missing values. But it can't be initialized without nulls.

class Serie(T)
  def initialize(@array = Array(T?).new); end

  def self.[](*values : T); Serie(T).new values.to_a.as(Array(T?)) end
  def self.[](*values : T?); Serie(T).new values.to_a end
end

Serie[1, nil, 2] # Works    
Serie[1, 2]      # Throws an Error

Also, it can't be initialized with array without explicit types and throws an error. Although it seems like there's enough information to infer the type.

Serie.new(Int32?)([1, nil, 2]) # Works
Serie.new([1, nil, 2])        # Throws an Error

P.S.

I'm using a hack to initialize it with Serie.self[...] do you know a way to initialize it as Array-like object Serie{1, 2} but avoid adding << method because it's static immutable time series data?


Solution

  • Array(T).new.as(Array(T?)) can't work because Array(T?) is not a valid restriction of Array(T) (Crystal's generics can't express covariance yet).

    In order to transform from Array(T) to Array(T?) you need to actually create a new array of type Array(T?) and copy the items over. One way to do that is values.map(&.as(T?)).

    (Technically, you could also use unsafe type casts to get an instance of Array(T?) pointing at the same memory as the Array(T) because the binary representation is identical; but it easily breaks when adding nil values via the nilable instance.)This is not actually true, the binary representation is different because of the union type.

    I'm using a hack to initialize it with Serie.self[...] do you know a way to initialize it as Array-like object Serie{1, 2} but avoid adding << method because it's static immutable time series data?

    Adding a self.[] method is probably the best workaround for that. There is a discussion about changing the implementation of array-like literals to call a single method instead of transforming to a series of << calls in #5703.