# Is there a way to combine special argument types that denote size through macros?

**URL:** <https://forum.crystal-lang.org/t/is-there-a-way-to-combine-special-argument-types-that-denote-size-through-macros/4277>\
**Category:** Help & Support\
**Created:** [January 19, 2022, 4:03am UTC](https://forum.crystal-lang.org/t/is-there-a-way-to-combine-special-argument-types-that-denote-size-through-macros/4277 "2022-01-19T04:03:50Z")\
**Posts on this page:** 13\
**Page:** 1

<div class="post-metadata">

**Author:** ![SleepingInsomniac](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/sleepinginsomniac/32/1442_2.png) [@SleepingInsomniac](https://forum.crystal-lang.org/u/SleepingInsomniac)\
**Post date:** [January 19, 2022, 4:03am UTC](https://forum.crystal-lang.org/t/is-there-a-way-to-combine-special-argument-types-that-denote-size-through-macros/4277/1 "2022-01-19T04:03:50Z")

</div>

A code example would speak best to what I’m trying to achieve:

`Crystal 1.3.0 (2022-01-06)`

```auto
struct Matrix(T, W, H)
  {% begin %}
    property values : StaticArray(T, {{W * H}})
  {% end %}

  def initialize
    @values = StaticArray(T, {{ W * H }}).new(T.new(0))
  end
end

```

Is there a way to specify (through macros, or otherwise?) that the `StaticArray` type is `W * H` in size somehow?

I could use `values : Slice(T)` or `values : Pointer(T)` but in benchmarking the allocation and multiplication of various collection types in this struct, my results showed StaticArray to be faster than pointers:

```auto
                   user system total real
pointer 0.243436 0.235193 0.478629 ( 0.254651)
slice 0.234756 0.220551 0.455307 ( 0.244790)
static array 0.117237 0.151175 0.268412 ( 0.124297)
tuple 0.116266 0.147461 0.263727 ( 0.122882)
vars 0.118009 0.153231 0.271240 ( 0.125376)

```

I’ve tried using the `TypeNode#type_vars` macro method, but that seems to return a literal `W * H` in this usage, which isn’t valid.

However, using this same approach in methods returns what I’d expect:

```auto
  struct Matrix(T, W, H)
    def self.size
      {{ @type.type_vars[1] * @type.type_vars[2] }}
    end

    def size
      {{ @type.type_vars[1] * @type.type_vars[2] }}
    end
  end

  Matrix(Int32, 3, 3).size # => 9
  Matrix(Int32, 3, 3).new.size # => 9

```

Appreciate any help/insight, thank you!

---

<div class="post-metadata">

**Author:** ![asterite](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/asterite/32/60_2.png) [@asterite](https://forum.crystal-lang.org/u/asterite)\
**Post date:** [January 19, 2022, 12:31pm UTC](https://forum.crystal-lang.org/t/is-there-a-way-to-combine-special-argument-types-that-denote-size-through-macros/4277/2 "2022-01-19T12:31:47Z")

</div>

Hi!

There’s no way to do that right now. We can use this forum topic to discuss how we could allow that… I’m not sure! Maybe math operators could be allowed in type arguments.

---

<div class="post-metadata">

**Author:** ![jgaskins](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/jgaskins/32/2449_2.png) [@jgaskins](https://forum.crystal-lang.org/u/jgaskins)\
**Post date:** [January 19, 2022, 3:06pm UTC](https://forum.crystal-lang.org/t/is-there-a-way-to-combine-special-argument-types-that-denote-size-through-macros/4277/3 "2022-01-19T15:06:08Z")

</div>

Interesting, I thought numbers in the `type_vars` list were hardcoded to only be allowed for `StaticArray`. TIL!

---

<div class="post-metadata">

**Author:** ![straight-shoota](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/straight-shoota/32/36_2.png) [@straight-shoota](https://forum.crystal-lang.org/u/straight-shoota)\
**Post date:** [January 19, 2022, 4:07pm UTC](https://forum.crystal-lang.org/t/is-there-a-way-to-combine-special-argument-types-that-denote-size-through-macros/4277/4 "2022-01-19T16:07:07Z")

</div>

I remember there was once a proposal about adding arithmetic expressions to the type grammar. But I can’t find it in the depths of GitHub issues.

---

<div class="post-metadata">

**Author:** ![SleepingInsomniac](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/sleepinginsomniac/32/1442_2.png) [@SleepingInsomniac](https://forum.crystal-lang.org/u/SleepingInsomniac)\
**Post date:** [January 21, 2022, 4:53am UTC](https://forum.crystal-lang.org/t/is-there-a-way-to-combine-special-argument-types-that-denote-size-through-macros/4277/5 "2022-01-21T04:53:38Z")

</div>

Thanks for the replies, I searched the issues, but couldn’t find a request either so I created a feature request on GitHub.

---

<div class="post-metadata">

**Author:** ![straight-shoota](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/straight-shoota/32/36_2.png) [@straight-shoota](https://forum.crystal-lang.org/u/straight-shoota)\
**Post date:** [January 21, 2022, 11:46am UTC](https://forum.crystal-lang.org/t/is-there-a-way-to-combine-special-argument-types-that-denote-size-through-macros/4277/6 "2022-01-21T11:46:36Z")

</div>

Please leave cross-references between related discussions so people can find them later:

> <https://github.com/crystal-lang/crystal/issues/11759>
>
> \## Feature Request
> 
> \`StaticArray(T, S)\` has an interesting property where the …size of the array is specified in the type grammar. I tried to follow this pattern in a 2 dimensional matrix struct (below), however it appears that this results in a syntax error.
> 
> \`\`\`crystal
> struct Matrix(T, W, H)
> property values : StaticArray(T, W \* H)
> 
> def initialize(@values)
> end
> end
> \`\`\`
> 
> I think allowing this type of expression in the type grammar would be a nice improvement to the language.

---

<div class="post-metadata">

**Author:** ![konovod](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/konovod/32/229_2.png) [@konovod](https://forum.crystal-lang.org/u/konovod)\
**Post date:** [January 23, 2022, 9:30am UTC](https://forum.crystal-lang.org/t/is-there-a-way-to-combine-special-argument-types-that-denote-size-through-macros/4277/7 "2022-01-23T09:30:45Z")

</div>

Maybe that’s not what you want, but you can do

```crystal
macro def_struct(w, h)
  struct Matrix{{w}}x{{h}}(T)
    property values : StaticArray(T, {{w * h}})

    def initialize
      @values = StaticArray(T, {{ w * h }}).new(T.new(0))
    end
  end
end

{% begin %}
  {% for i in 1..10 %} # alternatively, limit to actually useful sizes
    {% for j in 1..10 %}
      def_struct({{i}}, {{j}})
    {% end %}
  {% end %}
{% end %}

# ...
a = Matrix3x3(Int32).new
puts a

b = Matrix5x1(Int32).new
puts b

```

---

<div class="post-metadata">

**Author:** ![konovod](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/konovod/32/229_2.png) [@konovod](https://forum.crystal-lang.org/u/konovod)\
**Post date:** [January 23, 2022, 7:35pm UTC](https://forum.crystal-lang.org/t/is-there-a-way-to-combine-special-argument-types-that-denote-size-through-macros/4277/8 "2022-01-23T19:35:27Z")

</div>

Well, there is even better way: StaticArray of StaticArrays. It is more convenient to use (`@values[1][2]`) and have same layout in memory:

```crystal
struct Matrix(T, W, H)
  property values : StaticArray(StaticArray(T, H), W)

  def initialize
    @values = StaticArray(StaticArray(T, H), W).new(StaticArray(T, H).new(T.new(0)))
  end

  def to_plain
    {% begin %}
    @values.unsafe_as(StaticArray(T, {{ W * H }}))
  {% end %}
  end
end

a = Matrix(Int32, 3, 3).new
puts a
puts a.to_plain
puts sizeof(Matrix(Int32, 3, 3)) / sizeof(Int32)

```

---

<div class="post-metadata">

**Author:** ![SleepingInsomniac](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/sleepinginsomniac/32/1442_2.png) [@SleepingInsomniac](https://forum.crystal-lang.org/u/SleepingInsomniac)\
**Post date:** [January 23, 2022, 7:51pm UTC](https://forum.crystal-lang.org/t/is-there-a-way-to-combine-special-argument-types-that-denote-size-through-macros/4277/9 "2022-01-23T19:51:47Z")

</div>

I did attempt this approach, however it comes with the caveat that setting a specific value requires you to overwrite the entire sub-static array:

```auto
    def []=(col : Int, row : Int, value : T)
      c = @values[col] # "checkout" row for changes
      c[row] = value # set the col value within the row
      @values[col] = c # reassign the changed row to the static array of rows
    end

```

I also must be doing a large number of allocations by doing this because the benchmarks showed this approach to actually be slower than `@values : Slice(T)`

---

<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:** [January 23, 2022, 8:21pm UTC](https://forum.crystal-lang.org/t/is-there-a-way-to-combine-special-argument-types-that-denote-size-through-macros/4277/10 "2022-01-23T20:21:41Z")

</div>

This gave me the idea of trying

```crystal
pp! StaticArray(Int8, sizeof(StaticArray(StaticArray(Int8, 5), 4))) # => StaticArray(Int8, 20)

```

and it works, but when trying to use the matrix it fails ☹

```auto
struct Matrix(T, W, H)
  property values : StaticArray(T, sizeof(StaticArray(StaticArray(T, W), H)))

  def initialize
    @values = StaticArray(T, sizeof(StaticArray(StaticArray(T, W), H))).new(T.new(0))
  end
end

pp! Matrix(Int8, 4, 5).new.values

pp! Matrix(Int32, 2, 3).new.values

```

```auto
% crystal matrix.ign.cr
BUG: unknown node in TypeLookup: sizeof(StaticArray(StaticArray(T, W), H)) SizeOf (Exception)
  from raise<Exception>:NoReturn
  from raise<String>:NoReturn
  from Crystal::Type::TypeLookup#lookup<Cr

```

Almost!

---

<div class="post-metadata">

**Author:** ![konovod](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/konovod/32/229_2.png) [@konovod](https://forum.crystal-lang.org/u/konovod)\
**Post date:** [January 24, 2022, 3:45pm UTC](https://forum.crystal-lang.org/t/is-there-a-way-to-combine-special-argument-types-that-denote-size-through-macros/4277/11 "2022-01-24T15:45:09Z")

</div>

what about this? `Slice` would add runtime bounds check (`Pointer` won’t but this is asking for bugs) but still no allocations, so it should be fast.

```crystal
struct Matrix(T, W, H)
  property values : StaticArray(StaticArray(T, W), H)

  def initialize
    @values = StaticArray(StaticArray(T, W), H).new(StaticArray(T, W).new(T.new(0)))
  end

  private def to_slice
    pointerof(@values).unsafe_as(Pointer(T)).to_slice(W*H)
  end

  def []=(col : Int, row : Int, value : T)
    to_slice[row*W + col] = value
  end

  def [](col : Int, row : Int) : T
    @values[row][col] # of course to_slice[row*W + col] would work too.
  end
end

a = Matrix(Int32, 3, 7).new
a[1, 2] = 1
a[2, 1] = 2
puts a, a[1, 2], a[2, 1]

```

---

<div class="post-metadata">

**Author:** ![RespiteSage](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/respitesage/32/793_2.png) [@RespiteSage](https://forum.crystal-lang.org/u/RespiteSage)\
**Post date:** [January 24, 2022, 3:52pm UTC](https://forum.crystal-lang.org/t/is-there-a-way-to-combine-special-argument-types-that-denote-size-through-macros/4277/12 "2022-01-24T15:52:07Z")

</div>

This is my crack at it:

```crystal
struct FastMatrix(T, W, H, C)
  property values : StaticArray(T, C)
  
  def initialize
    {% if C != W * H %}
      {% raise "Invalid FastMatrix! C must be the product of W and H!" %}
    {% end %}
    @values = StaticArray(T, C).new(T.new(0))
  end
end

module Matrix(T, W, H)
  def self.new
    {% begin %}
      {% c = @type.type_vars[1] * @type.type_vars[2] %}
      FastMatrix(T, W, H, {{c}}).new
    {% end %}
  end
end

mat = Matrix(Float64, 3, 2).new

p! mat.values.size # => 6

```

[Run it with carc.in](https://carc.in/#/r/cnqq)

Edit:

There might be a more elegant way to do it, but at least you can check at compile-time to make sure that the size is right.

Oh, and you could even just make a little macro that makes a `FastMatrix`, too:

```crystal
macro new_mat(t, w, h)
  FastMatrix({{t}}, {{w}}, {{h}}, {{w * h}}).new
end

```

[Run it with carc.in](https://carc.in/#/r/cnqt)

---

<div class="post-metadata">

**Author:** ![konovod](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/konovod/32/229_2.png) [@konovod](https://forum.crystal-lang.org/u/konovod)\
**Post date:** [January 25, 2022, 8:54am UTC](https://forum.crystal-lang.org/t/is-there-a-way-to-combine-special-argument-types-that-denote-size-through-macros/4277/13 "2022-01-25T08:54:45Z")

</div>

The problem in your solution is that sometimes explicit type is needed.  
E.g. for method arguments restriction:

```crystal
def apply_matrix(m1 : Matrix(Float64, 3, 3)) # not working
def apply_matrix(m1 : FastMatrix(Float64, 3, 3, 9)) # isn't convenient

```

This can be solved though by including Matrix in FastMatrix:

```crystal
module Matrix(T, W, H)
  def self.new
    {% begin %}
      {% c = @type.type_vars[1] * @type.type_vars[2] %}
      FastMatrix(T, W, H, {{c}}).new
    {% end %}
  end
end

struct FastMatrix(T, W, H, C)
  include Matrix(T, W, H)
  property values : StaticArray(T, C)
  
  def initialize
    {% if C != W * H %}
      {% raise "Invalid FastMatrix! C must be the product of W and H!" %}
    {% end %}
    @values = StaticArray(T, C).new(T.new(0))
  end
end

def apply(m : Matrix(Float64, 3, 3))
  p! m
end

mat = Matrix(Float64, 3, 2).new
other_mat = Matrix(Float64, 3, 3).new
# apply(mat) - correctly rejected
apply(other_mat)

```

But I’m not sure that it won’t break at some point later (for example, it doesn’t compile if FastMatrix declared before Matrix).
