# Capture block forwarding between two classes

**URL:** <https://forum.crystal-lang.org/t/capture-block-forwarding-between-two-classes/5564>\
**Category:** Help & Support\
**Created:** [April 13, 2023, 7:59pm UTC](https://forum.crystal-lang.org/t/capture-block-forwarding-between-two-classes/5564 "2023-04-13T19:59:24Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![hutou](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/hutou/32/1337_2.png) [@hutou](https://forum.crystal-lang.org/u/hutou)\
**Post date:** [April 13, 2023, 7:59pm UTC](https://forum.crystal-lang.org/t/capture-block-forwarding-between-two-classes/5564/1 "2023-04-13T19:59:24Z")

</div>

Here is an example of code reduced to the simplest, but representative of what I want to achieve in my project, a “capture block forwarding” between 2 classes. Of course, as it is, it is not functional!

```crystal
class Table(T)
  @column_registry = {} of String => Column(T)

  def initialize(@sources : Enumerable(T))
  end

  def to_s
    @sources.each do |source|
      @column_registry.each do |label, column|
        puts column.extracted_data(source)
      end
    end
  end

  def add_column(label : String, header : String, &extractor)
    @column_registry[label] = Column(T).new(header, extractor)
  end
end

class Column(T)
  def initialize(@header : String, @extractor : T -> _)
  end

  def extracted_data(source)
    @extractor.call(source)
  end
end

t = Table.new([[1, "abc", 3.14]])
t.add_column("A", "header") { |n| n[1] }
puts t.to_s

```

The error I get is:

> error in line 30  
> Error: wrong number of block parameters (given 1, expected 0)

I’m stuck on this ☹

---

<div class="post-metadata">

**Author:** ![Blacksmoke16](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/blacksmoke16/32/1241_2.png) [@Blacksmoke16](https://forum.crystal-lang.org/u/Blacksmoke16)\
**Post date:** [April 13, 2023, 8:49pm UTC](https://forum.crystal-lang.org/t/capture-block-forwarding-between-two-classes/5564/2 "2023-04-13T20:49:45Z")

</div>

I think you just need to type your captured block. E.g. `&extractor : T ->`. Otherwise Crystal assumes it doesn’t have any arguments and doesn’t return anything.

Ref: [Capturing blocks - Crystal](https://crystal-lang.org/reference/1.7/syntax_and_semantics/capturing_blocks.html)

---

<div class="post-metadata">

**Author:** ![hutou](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/hutou/32/1337_2.png) [@hutou](https://forum.crystal-lang.org/u/hutou)\
**Post date:** [April 13, 2023, 9:23pm UTC](https://forum.crystal-lang.org/t/capture-block-forwarding-between-two-classes/5564/3 "2023-04-13T21:23:53Z")

</div>

> [@hutou](#):
>
> `def add_column(label : String, header : String, &extractor)`

With `def add_column(label: String, header: String, &extractor: T -> )` the program runs, but returns `nil` (which is expected according to the documentation).

With `def add_column(label : String, header : String, &extractor : T -> _)`, the type of `extractor` is correctly identified, but it is not “transmitted” to the instantiation of `Column(T)`.

> The error is :
> 
> error in line 16  
> Error: instantiating ‘Column(Array(Float64 | Int32 | String)).class#new(String, Proc(Array(Float64 | Int32 | String), (Float64 | Int32 | String))’
> 
> error in line 21  
> Error: can’t infer the type of instance variable ‘@extractor’ of Column(Array(Float64 | Int32 | String))
> 
> Could you add a type annotation like this

> ```
> class Column(Array(Float64 | Int32 | String))
> @extractor : Type
> end
> 
> ```
> 
> replacing `Type` with the expected type of `@extractor`?

I got the program working by adding the correct type `(Int32 | String | Float64)` in both the `add_column` method and in the `Column initialize` method, but this is precisely what I want to avoid!

---

<div class="post-metadata">

**Author:** ![bcardiff](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/bcardiff/32/3_2.png) [@bcardiff](https://forum.crystal-lang.org/u/bcardiff)\
**Post date:** [April 13, 2023, 10:16pm UTC](https://forum.crystal-lang.org/t/capture-block-forwarding-between-two-classes/5564/4 "2023-04-13T22:16:56Z")

</div>

I think that the only alternative is to declare a `module CellValue`, make that module be included in all the types you expect to be used, and use that module as the return type of your procs.

Is a way to declare a union in an open way. If a new type comes to the game you don’t need to alter existing code.

You can then define methods like `def render_cell(io : IO)` that might come handy in the future.

---

<div class="post-metadata">

**Author:** ![hutou](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/hutou/32/1337_2.png) [@hutou](https://forum.crystal-lang.org/u/hutou)\
**Post date:** [April 14, 2023, 7:19am UTC](https://forum.crystal-lang.org/t/capture-block-forwarding-between-two-classes/5564/5 "2023-04-14T07:19:45Z")

</div>

Thanks for your answer.  
Following your recommendations, I now get a new error at line:

```auto
t.add_column("A", "header") { |n| n[1] }

```

The error is:

> error in line 37  
> Error: expected block to return CellType, not (Float64 | Int32 | String)

Here is my code :

```crystal
module CellType
end

class Table(T)
  include CellType
  @column_registry = {} of String => Column(T)

  def initialize(@sources : Enumerable(T))
  end

  def to_s
    @sources.each do |source|
      @column_registry.each do |label, column|
        puts column.extracted_data(source)
      end
    end
  end

  def add_column(label : String, header : String,
      &extractor : T -> CellType)
    @column_registry[label] = Column(T).new(header, extractor)
  end
end

class Column(T)
  include CellType
  def initialize(@header : String, @extractor : T -> CellType)
  end

  def extracted_data(source)
    @extractor.call(source)
  end
end

t = Table.new([[1, "abc", 3.14]])
t.add_column("A", "header") { |n| n[1] }
puts t.to_s

```

I must be missing something: how can the relationship between CellType and the actual type be performed?

---

<div class="post-metadata">

**Author:** ![Devonte](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/devonte/32/1927_2.png) [@Devonte](https://forum.crystal-lang.org/u/Devonte)\
**Post date:** [April 15, 2023, 12:18am UTC](https://forum.crystal-lang.org/t/capture-block-forwarding-between-two-classes/5564/6 "2023-04-15T00:18:34Z")

</div>

`module CellType` is not a subtype of `String | Int32 | Float64` so that’s not really possible. I would say go with an `alias CellType = String | Int32 | Float64` which you can then cast to `CellType` in your block if necessary, then handle the different value types of `@column_registry` accordingly.

---

<div class="post-metadata">

**Author:** ![bcardiff](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/bcardiff/32/3_2.png) [@bcardiff](https://forum.crystal-lang.org/u/bcardiff)\
**Post date:** [April 17, 2023, 5:58pm UTC](https://forum.crystal-lang.org/t/capture-block-forwarding-between-two-classes/5564/7 "2023-04-17T17:58:55Z")

</div>

What @Devonte says.

Here is an adapted version of the code. It allows overriding render\_cell as needed for each type.

```auto
module CellType
  def render_cell(io : IO)
    to_s(io)
  end
end

struct Float32
  include CellType
end

struct Int32
  include CellType
end

class String
  include CellType
end

class Table(T)
  include CellType
  @column_registry = {} of String => Column(T)

  def initialize(@sources : Enumerable(T))
  end

  def to_s(io : IO)
    @sources.each do |source|
      @column_registry.each do |label, column|
        column.extracted_data(source).render_cell(io)
      end
    end
  end

  # R will be forced to be convertible to CellType
  # but to allow blocks returning individual types
  # for this extractor argument we need to the cast
  # internally. All is typesafe still \o/
  def add_column(label : String, header : String,
                 &extractor : T -> R) forall R
    @column_registry[label] = Column(T).new(header,
      Proc(T, CellType).new { |row|
        extractor.call(row).as(CellType)
      }
    )
  end
end

class Column(T)
  include CellType

  def initialize(@header : String, @extractor : T -> CellType)
  end

  def extracted_data(source)
    @extractor.call(source)
  end
end

t = Table.new([[1, "abc", 3.14]])
t.add_column("A", "header") { |n| n[1] }
puts t.to_s

```

---

<div class="post-metadata">

**Author:** ![hutou](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/hutou/32/1337_2.png) [@hutou](https://forum.crystal-lang.org/u/hutou)\
**Post date:** [April 17, 2023, 8:15pm UTC](https://forum.crystal-lang.org/t/capture-block-forwarding-between-two-classes/5564/8 "2023-04-17T20:15:34Z")

</div>

Wow, great!  
I just added a macro to automatically include CellType in all standard types.  
Thanks a lot, really.

NB. I just have to study a little bit the subtlety of the code of the add\_column method 🤔 🙂 🙂
