Why following flatten_type method work?

I saw some code in this post, but i don’t know why this code work as expected.(flatten the type)

def flatten_type(object)
  if object.is_a?(Array)
    flatten_type(object[0])
  else
    object
  end
end

puts typeof(flatten_type(1))                          #=> Int32
puts typeof(flatten_type([1, [2]]))                   #=> Int32
puts typeof(flatten_type([1, [2, ['a', 'b']]]))       #=> Int32 | Char

Any idea? Thanks.

What part of it is suprising do you? What do you not understand?

Let us use following code as a example:

def flatten_type(object)
  if object.is_a?(Array)
    flatten_type(object[0])
  else
    object
  end
end

x = [1, [2, ['a', 'b']]]
puts x[0].is_a? Array                           # => false
puts typeof(x[0])                               # => (Array(Array(Char) | Int32) | Int32)
puts typeof(flatten_type([1, [2, ['a', 'b']]])) # => Int32 | Char

I can’t understand how flatten_type can flatten the nested type.

x = [1, [2, [‘a’, ‘b’]]]; the object[0] is x[0], it is 1, right? the type of 1 is (Array(Array(Char) | Int32) | Int32), how it change to Int32 | Char ?

Thanks

Hi! You can read about it here: if var.is_a?(...) - Crystal

Let us know if it’s still not clear.

Yes, still not clear, I think I lack imagination in some way.

let me try guess this code step by step.(crystal i not work for me for now because missing LLVM 15 support)

  • x is [1, [2, [‘a’, ‘b’]]]
  • Try run flatten_type(x)
  • x.is_a? Array => Yes
  • try run flatten_type(x[0]), because x[0] is 1, so, should be flatten_type(1).
  • 1.is_a? Array => No
  • So, flatten_type finally return object, it is 1,
  • the compile-time type of 1 should be (Array(Array(Char) | Int32) | Int32)

Why the result is Int32 | Char ?

This is the part that’s actually slightly different.

The compiler doesn’t know what’s in the first position of the array. You know x[0] is an int, but for the compiler that could be an int or the other array.

It’s easier if you also print the types as the compiler figures things out:

def flatten_type(object : T) forall T
  {% puts "typeof(object) is #{T}" %}
  if object.is_a?(Array)
    show_typeof_object0(object[0])
    flatten_type(object[0])
  else
    show_typeof_object(object)
    object
  end
end

def show_typeof_object0(x : T) forall T
  {% puts "typeof(object[0]) is #{T}" %}
end

def show_typeof_object(x : T) forall T
  {% puts "typeof(object) is #{T}" %}
end

flatten_type([1, [2, ['a', 'b']]])

The output is:

typeof(object) is Array(Array(Array(Char) | Int32) | Int32)
typeof(object[0]) is (Array(Array(Char) | Int32) | Int32)
typeof(object) is (Array(Array(Char) | Int32) | Int32)
typeof(object[0]) is (Array(Char) | Int32)
typeof(object) is (Array(Char) | Int32)
typeof(object[0]) is Char
typeof(object) is Char
typeof(object) is Char
typeof(object) is Int32

Another way to understand this:

a = [1, 'a']
puts typeof(a[0]) # => Char | Int32

That is, typeof(a[0]) is not Int32, it’s the union. The compiler can’t know (in general) what’s in each position of the array.

1 Like