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
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!
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:
Another example, outside of tuple:
Basically, a
*Trestriction (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 isTuple.new.In fact, one can also do:
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:
I believe this is a good solution to this problem because it's a general solution and kind of intuitive :-)
Relevants commits: