class Array
def self.elem_type(typ)
if typ.is_a?(Array)
puts "typ arr go #{typ}"
elem_type(typ.first) #2
else
puts "typ go #{typ}"
typ #1
end
end
end
nest = [1, ["b", [:c, ['d']]]]
#p Array.elem_type(nest)
p typeof(Array.elem_type(nest))
it outputs (Char | Int32 | String | Symbol),
I’m wrapping my head over it why it can flatten the type in nested array.
so is compile time compiler first checks:
(nest) # => Array(Int32 | Array(String | Array(Symbol | Array(Char)))) ,
take #1 will have Int32
take #2 will have Array(String | Array(Symbol | Array(Char))
so it will becomes Int32 | String | Array(Symbol | Array(Char)),
then another round compiler goes to Int32 | String | Symbol | Array(Char) like that,
then another round compiler goes to Int32 | String | Symbol | Char ?
This smells like tail-call optimization to me, flattening out all the calls into a single call. You get a very different outcome if you handle the different types in different methods:
class Array
def self.elem_type(typ : Array)
puts "typ arr go #{typ}"
elem_type(typ.first) # 2
end
def self.elem_type(typ)
puts "typ go #{typ}"
typ # 1
end
end
nest = [1, ["b", [:c, ['d']]]]
p typeof(Array.elem_type(nest))
# (Array(Array(Array(Char) | Symbol) | String) | Array(Array(Char) | Symbol) | Array(Char) | Char | Int32 | String | Symbol)
TCO is a runtime technique. The evaluation of typeof happens entirely at compile time. You’ll notice that the puts calls don’t actually print anything, because the method never executes. It’s just used for typing.
Also, the recursive call isn’t even the last expression in the method.
The behaviour you see with your edited example is a bit confusing. But there’s a difference between overloading and branching on is_a?.
I’m not sure what exactly the overloading is doing here and my expectation would be that the result should be the same as before.
It works if you add type restrictions to the second method (self.elem_type(typ : Char | Int32 | String | Symbol)).