I was using Enumerable#select(&block) to try to refine an Array to elements of a certain type with arr.select { |el| el.is_a?(Bar) }
but that wasn't working.
I then saw the Enumerable#select(type) method, and that worked: https://play.crystal-lang.org/#/r/7v05
But I noticed that the two definitions are quite similar in my case.
def select(&block : T ->)
ary = [] of T
each { |e| ary << e if yield e }
ary
end
def select(type : U.class) forall U
ary = [] of U
each { |e| ary << e if e.is_a?(U) }
ary
end
Is there a way to let the compiler know that the select block is refining the type of the elements (maybe by adding a type to the block somehow)? Or is there a reason the compiler can't know about what the block is asserting?
The issue here is that the array needs to be created with the right type. So the core difference of the two methods is:
ary = [] of T
where T
is the type argument of the Enumerable
you invoke select on, versus
ary = [] of U
where U
is the type argument specific to this method (forall U
).
So to do what you want, we would need to know that the block is filtering the elements, but that's in no way encoded in the blocks type. It only has a list of argument types and a return type. However, we could certainly combine the two methods into something like:
module Enumerable(T)
def select(type : U.class) forall U
ary = [] of U
each { |e| ary << e if yield e if e.is_a? U }
ary
end
end