crystal-lang / crystal

The Crystal Programming Language
https://crystal-lang.org
Apache License 2.0
19.26k stars 1.61k forks source link

Add arithmetic expressions to the type grammar #11759

Open SleepingInsomniac opened 2 years ago

SleepingInsomniac commented 2 years ago

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.

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.

straight-shoota commented 2 years ago

Previous discussion on the forum: https://forum.crystal-lang.org/t/is-there-a-way-to-combine-special-argument-types-that-denote-size-through-macros/4277

asterite commented 2 years ago

Thinking about how to implement this in the most generic way...

A type like StaticArray(T, 3) is represented with type vars TypeParameter("T") for the first one, and NumberLiteral(3) for the second one. I guess we could introduce another type parameter like CallParameter similar to a call. In this case it would be something like CallParameter(TypeParameter("W"), "*", [TypeParameter("H")]). Then when instantiating this parameter with both W and H number literals we can do the math and produce a new NumberLiteral. We could support all math operations here, but I don't know if much more.

Also, I'm not sure how much complexity this adds to the language and whether it's worth it.

jwoertink commented 2 years ago

If this does get added, it would be good to also resolve https://github.com/crystal-lang/crystal/issues/4238. Since the type parameter has to be a literal, passing in a variable leads to an unexpected token error which can be a bit confusing.

straight-shoota commented 2 years ago

Thanks @jwoertink, that reference brought me to #5427. I knew there had already been an issue for this 😀 Let's continue the discussion about arithmetic expressions here, though. It's more focused.

HertzDevil commented 1 month ago

You could somewhat circumvent multiplication by using sizeof, but it is a bit tricky since you need an extra type to store the result:

module Matrix(T, W, H)
  struct Impl(T, W, H, WH)
    include Matrix(T, W, H)

    @values = uninitialized T[WH]
  end

  def self.new
    Impl(T, W, H, sizeof(UInt8[W][H])).new
  end
end

Matrix(Int32, 3, 2).new # => Matrix::Impl(Int32, 3, 2, 6)(@values=StaticArray[0, 0, 0, 0, 0, 0])

Addition works similarly by sizeof(Tuple(UInt8[X], UInt8[Y])).