RFC: %wt(hey ho) == {"hey, "ho"}

I was porting some ruby code to crystal at work that had constants declared as:

FOO = %w(hey ho)

The natural Crystal version of this is

FOO = {"hey, "ho"}

So I was thinking if would be nice to have a %something to declare tuples of strings. First I was thinking about having a %t to do that, but it is misleading, t reminds tuple however tuples can be made from anything, so %t(1 2 3) can confuse people thinking is translates to {1, 2, 3} instead of {"1", "2", "3"}.

So maybe %wt(hey ho) is better, wt => words tuple. Is this reasonable? Do we already have too many %things?

1 Like

I think this would be too much.
Also the natural Crystal version is already %w(hey ho) == ["hey", "ho"]. IMO this is not a typical use case for tuples.

I would not disagree about the “too much”, since there are %r, %q, %Q, %w, %W and %i that I remember…

But I disagree about the natural Crystal version, since it’s a constant it’s better to have the data in a static array or a tuple to avoid memory allocation.

For constants, tuples are a great way to avoid accidental mutation — for example, when a misguided developer uses FOO << "bar" instead of FOO + ["bar"] or other similar mutation. We don’t have Ruby’s freeze in Crystal so we can’t protect arrays against modification.

With this we could do Slice["hey", "ho"].freeze, although that Slice is still on the heap.

Also you might want to try this:

macro const_slice(assign)
  CONST%arr = StaticArray[{{ assign.value.splat }}]
  {{ assign.target }} = Slice.new(CONST%arr.to_unsafe, CONST%arr.size, read_only: true)
end

const_slice FOO = ["hey", "ho"]

You could see that FOO is indeed in writable global memory, but Slice itself is immutable, and CONST__temp_1 is normally inaccessible by the user.

1 Like

I consider following code is a mistake in ruby.

FOO = ['a', 'b']

FOO << 'c'

Though, array is a reference, but, why we want to use a const like Foo < 'c' ? Why we are not consider FOO is frozen as default? i don’t think there is any benefit.

if frozen as default, user can copy code like FOO = %w(hey ho) directly from ruby, and compiler can deduce, 1. it is a const. 2. it length is small, we can allocate it directly in stack (same as FOO = {'a', 'b'})