Since the implementation MyTest#each has a yield, it changes the inferred “type signature” of the method, which then no longer matches the vanilla abstract def each which does not declare that the implementation will yield (and thus require a block when called.)
(Btw, I was surprised that I could use T in that abstract class that is not explicitly “generic”; so there is some nuance to the type inference that I don’t yet understand. )
The important part is designating the abstract method to be yielding. The given signature corresponds to a non-yielding method, but you only implementation does yield.
The correct signature for that would be abstract def each(& : Int32 ->).
The version with T works, but I think that’s more of an accident. The abstract method is never instantiated, so the compiler doesn’t need to resolve the type. If it would, it would discover that it doesn’t exist. But it’s enough to inform the abstract method def checker
Is there a way, besides making Test explicitly generic, to declare the abstract method’s yield/block without a concrete type (i.e. Int32) so that a future class can yield anything?
## this does not compile
abstract def each(& ->)
I’m more curious than anything else as I’ve been able to declare a method arg without a type and have it aggregate all future calls’ arg types (which is pretty nifty).
That was the first thing I tried and it did not work. I’ve simplified the example below and identified what I tried.
abstract class Test
abstract def each(& : Int32 ->) # this works
#abstract def each(&) # this does not
#abstract def each(& ->) # this does not
#abstract def each # this does not
end
class MyTest < Test
def each
yield 10
end
end
Interestingly, as my first attempt using T showed, that type could be anything, as the specific type isn’t as important as the structure of the declaration which sets up the abstract method definition for later type checking whe implementing the subclass.
(One of the fascinating things about Crystal is how the compiler defers some syntax/semantic checking until when a method needs to have its code generated. This is tree-shaking at its best.)
Okay, I think i see what’s going on. each(&) is technically not matched with your implementation because it is yielding a value where the abstract def is just looking for a method that yields, but with no args. In the case of wanting to restrict the method to yield something, you could do abstract def each(& : _ ->) which basically says it yields one argument of any type, and returns nil. E.g. yield 10 or yield "foo" would satisfy it, but not yield 10, "foo" (two arguments).