No overload matches for Array(Int32) with method taking Array(T)

I have defined two simple methods, one that takes an array of arrays of whatever and another that takes an array of whatever.
The compiler complains that when given an array of Int32 the Array(T) implementation does not match. Shouldn’t it?

def foo(arr : Array(Array(T)))
  p "This was an array of arrays"
end

def foo(arr : Array(T))
  p "This was just an array"
end

If I call foo([0,1,2,3]) I would expect the output to be "This was just an array" rather than the error above. I suspect I have missed something integral to generic type implementation but can’t figure it out from the docs.

The error message is wrong. Please report a bug in Github.

To fix your code, add forall T after each method definition.

Actually, could you provide reproducible code? The code you gave doesn’t compile, it has a syntax error.

Once I fix the syntax error, there’s no error because you never call the method. But when I call it I see a good error.

module Foo(T)

  extend self

  def foo(arr : Array(Array(T)))

    p "This was an array of arrays"

  end

  def foo(arr : Array(T))

    p "This was just an array"

  end

end

Foo.foo([1,2,3])

on the play.crystal-lang.org site gives

error in line 13
Error: no overload matches 'Foo(T).foo' with type Array(Int32)

Overloads are:
 - Foo(T)#foo(arr : Array(Array(T)))
 - Foo(T)#foo(arr : Array(T))

btw, I think I fixed the missing paren in the OP.

What are you trying to do? You don’t need that to be Foo(T), just Foo, then use forall T in method definitions.

I don’t think that solves the problem.

module Foo

 

  extend self

 

  def foo(arr : Array(Array(T))) forall T

 

    p "This was an array of arrays"

 

  end

 

  def foo(arr : Array(T)) forall T

 

    p "This was just an array"

 

  end

 

end

 

p Foo.foo([1,2,[3,4]])

returns the error:

Error: no overload matches 'Foo.foo' with type Array(Array(Int32) | Int32)

Overloads are:
 - Foo#foo(arr : Array(Array(T)))
 - Foo#foo(arr : Array(T))

That error is correct since none of your overloads can handle the type of Array(Array(Int32) | Int32). You would need to define another overload like:

  def foo(arr : Array(Array(T) | T)) forall T
    p "This was an array of arrays or values"
  end

Because [1, 2, [1,2]] is NOT the same type of [[1,2]].

1 Like

This is the bug. You need to tell the module that T is Int32:

Foo(Int32).foo([1, 2, 3])
Foo(Int32).foo([[1], [2, 3]])

It would be much simpler if you tell us what you want to accomplish. You probably don’t need multiple overloads to code your algorithm.

I really would like to accomplish understanding how generics work in Crystal. The problem is that Arrays are not strongly typed, so I don’t know how to reason about them when some aspects of the language are strongly typed. Because you can have an Array of various types ([1, "a", [2,"b"]], for example) making a method for Array(T) is very difficult for me to figure out. What I really want is to call one method when T is U and another when T is [U]. How does one do that?

def foo(a : Array(T)) forall T
  a.each do |a|
    foo(a)
  end
end

def foo(a : T) forall T
  p a
end  

foo([1, [2, [[3]], [4, [[5]]], 6, 7], 8])

produces the output

1
2
[[3]]
4
5
6
7
8

Why is the [[3]] not simply 3 since [4,[[5]] produces 4 and 5 as expected? I should be able to have a method for T where T < [U] or something similar.

The thing is, when you have Array(Int32) | Array(Array(Int32)), which is something that happens in your code, it doesn’t match T because there’s no way to find a single T for that.

Instead, make the code simpler:

def foo(a : Array)
  a.each do |a|
    foo(a)
  end
end

def foo(a)
  p a
end

foo([1, [2, [[3]], [4, [[5]]], 6, 7], 8])

Or even:

def foo(a)
  if a.is_a?(Array)
    a.each do |a|
      foo(a)
    end
  else
    p a
  end
end

foo([1, [2, [[3]], [4, [[5]]], 6, 7], 8])

In most cases T, forall T, etc., are very advanced features and not needed.

Is it fair to say then that runtime type checks are preferred over compile time checks in Crystal?
And I still don’t understand the output from the example in my last post. Why does it not recurse into the [[3]] structure when it does for all the other nested arrays?

Compile-time checks are preferred. You can consider the behaviour in your snippet a bug or a limitation. Sorry, I don’t have more time to explain it but maybe someone else can.

I appreciate the time you’ve taken. I changed the snippet slightly so that the output reflects what I am confused about more clearly:

def foo(a : Array(T)) forall T
  a.each_with_index do |a, i|
    p "first call for #{a.class}"
    foo(a)
  end
end

def foo(a : T) forall T
  p "second call for class #{a.class}"
  p a
end  

foo([1, [2, [[3]], [4, [[5]]], 6, 7], 8])

returns

“first call for Int32”
“second call for class Int32”
1
“first call for Array(Array(Array(Array(Int32)) | Int32) | Array(Array(Int32)) | Int32)”
“first call for Int32”
“second call for class Int32”
2
“first call for Array(Array(Int32))”
“second call for class Array(Array(Int32))”
[[3]]
“first call for Array(Array(Array(Int32)) | Int32)”
“first call for Int32”
“second call for class Int32”
4
“first call for Array(Array(Int32))”
“first call for Array(Int32)”
“first call for Int32”
“second call for class Int32”
5
“first call for Int32”
“second call for class Int32”
6
“first call for Int32”
“second call for class Int32”
7
“first call for Int32”
“second call for class Int32”
8

Clearly the expected recursion is happening for [[5]] in a way it is not for [[3]], which is very confusing to me.

In the end, it’s a bug. I reported it: https://github.com/crystal-lang/crystal/issues/8973

1 Like

Cheers, thanks.

I think this is going to come down to an interesting problem of mathematical sets.
Define N as whole numbers.
Define A as groups of N.
Define B as groups of N | A.
Define C as groups of N | B | C.
With that recursive type C the “unit type” is going to be (N | B) even though the unit type of B is N. Any recursive type should trigger this “bug”, which might not really be a bug so much as unintuitive behavior.

That’s why [1, [2, [[3]], [4, [[5]]]]] triggers the bug but [1, [2, [[[3]], 4, [[5]]]]] does not. In the first [2, [[3]], [4, [[5]]]] is a recursive type. If we define

Z = Array(Array(Int32)) | Int32 | Z

then [2, [[3]], [4, [[5]]] is type Z but [2, [[[3]], 4, [[5]]]] is not. It is type

Y = X | Int32 where X = Array(Array(Int32)) | Int32

Because this is not a recursive type there is no bug.

1 Like

Unrelated, but the word/concept forall is somewhat hard to understand…even after reading the docs it didn’t become clear… :|

Do you know the concept of generic methods in other languages? For example foo in C# or Java. If so, it’s the same.

1 Like