Robert
Robert

Reputation: 784

Type refinement in Crystal from yielding blocks

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.

Enumerable#select(&block):

  def select(&block : T ->)
    ary = [] of T
    each { |e| ary << e if yield e }
    ary
  end

Enumerable#select(type)

  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?

Upvotes: 0

Views: 78

Answers (1)

Jonne Ha&#223;
Jonne Ha&#223;

Reputation: 4857

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

Upvotes: 2

Related Questions