Crystal: Tuple.new discards type information

Created on 27 Oct 2015  路  4Comments  路  Source: crystal-lang/crystal

Tuple.new always creates a new tuple type based on the exact (narrowest) types of its arguments, even if it's called on a specific Tuple subtype.

Tuple(Int32, Int32).new([1,2,3], "test")
puts typeof(foo)

# {Array(Int32), String}
alias MyValue = Int32 | Float64
alias MyTuple = {MyValue, MyValue}

def test(value : MyValue)
  MyTuple.new(5, value)
end

puts typeof(test(6.0))

# {Int32, Float64}

Thanks to _Tetsumi_ from IRC for finding this

bug compiler

Most helpful comment

I decided to implement this by allowing a splat restriction on a splat argument. So, after the next release (we can't do it right now because there's no syntax for this) we can write:

struct Tuple
  def self.new(*args : *T)
    args
  end
end

tup = Tuple.new(1, 2) # OK

Tuple(Int32, Int32).new(1, 2) # OK
Tuple(Int32, Int32).new(1, 'a') # Error

alias Point = {Int32, Int32}

Point.new(1, 2) # OK
Point.new(1, 'a') # Error

Another example, outside of tuple:

def foo(*args : *T)
  T
end

t = foo 1, 'a'
t # => {Int32, Char}

Basically, a *T restriction (which is only allowed on a splat argument, for now) will put the matching types into a tuple. I think this is kind of intuitive and similar to what * does for an argument. In any case, the main use for this is Tuple.new.

In fact, one can also do:

def foo(*args : *{T, U})
  p T
  p U
end

foo 1, 'a'
# prints Int32 and Char

though of course there's no real point in doing that, better to specify two arguments with a restriction each.

I also did the same for NamedTuple and double splats:

struct NamedTuple
  def self.new(**options : **T)
  end
end

alias Point = {x : Int32, y: Int32}

Point.new x: 1, y: 2 # OK
Point.new y: 1, x: 2 # OK
Point.new x: 1, y: 2, z: 3 # Error
Point.new x: 1, y: 'a' # Error

def foo(**options : **T)
  T
end

t = foo x: 1, y: 'a'
t # => {x: Int32, y: Char}

I believe this is a good solution to this problem because it's a general solution and kind of intuitive :-)

Relevants commits:

All 4 comments

There isn't a way to implement this right now. We'll probably need to have a way, at compile time, to know if the tuple is the generic type or an instantiation. If it's an instantiation we type check (we probably can use the cast method of types), otherwise we infer the types (the current approach).

And probably related to #1811. I guess if we do this then for that issue we could use:

thing, n = Tuple(String, Int64).new(JSON.parse(%(["world", 2])) as Array)

I decided to implement this by allowing a splat restriction on a splat argument. So, after the next release (we can't do it right now because there's no syntax for this) we can write:

struct Tuple
  def self.new(*args : *T)
    args
  end
end

tup = Tuple.new(1, 2) # OK

Tuple(Int32, Int32).new(1, 2) # OK
Tuple(Int32, Int32).new(1, 'a') # Error

alias Point = {Int32, Int32}

Point.new(1, 2) # OK
Point.new(1, 'a') # Error

Another example, outside of tuple:

def foo(*args : *T)
  T
end

t = foo 1, 'a'
t # => {Int32, Char}

Basically, a *T restriction (which is only allowed on a splat argument, for now) will put the matching types into a tuple. I think this is kind of intuitive and similar to what * does for an argument. In any case, the main use for this is Tuple.new.

In fact, one can also do:

def foo(*args : *{T, U})
  p T
  p U
end

foo 1, 'a'
# prints Int32 and Char

though of course there's no real point in doing that, better to specify two arguments with a restriction each.

I also did the same for NamedTuple and double splats:

struct NamedTuple
  def self.new(**options : **T)
  end
end

alias Point = {x : Int32, y: Int32}

Point.new x: 1, y: 2 # OK
Point.new y: 1, x: 2 # OK
Point.new x: 1, y: 2, z: 3 # Error
Point.new x: 1, y: 'a' # Error

def foo(**options : **T)
  T
end

t = foo x: 1, y: 'a'
t # => {x: Int32, y: Char}

I believe this is a good solution to this problem because it's a general solution and kind of intuitive :-)

Relevants commits:

@asterite, you're not a 10Xer, you're more like a 100xer!

Was this page helpful?
0 / 5 - 0 ratings