I assumed block type restrictions & : -> and & : -> _ to be pretty much equivalent.
And I couldn’t find any difference between them, except that underscore return type restriction doesn’t always work.
For example the following code does not compile:
def foo(& : -> _)
yield
end
def recursive
if false
foo { recursive } # Error: can't infer block return type, try to cast the block body with `as`.
end
end
recursive
But it compiles if the method signature is foo(& : -> ).
Am I missing any other differences?
If not, should they rather be equivalent in all regards? Because that only difference in behaviour seems rather odd.
Without _ type restriciton, the compiler figures the return type to be Nil. With _ type restriction, an explicit .as(Nil) needs to be added.
I’m mostly just wondering if there’s any other difference between both variants.
I take _ to mean ‘It returns any sort of value’, and that includes Nil, but the compiler is throwing because of the recursiveness of the function. If you did this with Generics it might be more clear:
class Test(C)
def foo(& : -> C)
yield # Return type is C
end
end
def recursive
if false
Test(Nil).new.foo { recursive }
end
end # Returns Nil | Nil, which is Nil.
recursive
In this case, there would be no compiler error because it can figure out the return type. In your code:
def foo(& : -> _)
yield # Return type is return type of block
end
def recursive # Return type is `return type of block | Nil`
if false
foo { recursive } # Return type of block contains itself, so compiler can't infer return type here.
end
# Here it returns Nil
end
recursive
It almost like a rogue generic, except here the generic references itself, meaning we can’t infer the return type of the block.