Crystal: Allow auto vivification for nested structured types

Created on 3 May 2017  路  5Comments  路  Source: crystal-lang/crystal

The idea is that we can use Crystal type system to support auto vivification of Hashes and Arrays like we have in perl.

Consider this:

@update_tm_to_product_property = Hash(Time,Hash(Int32,Hash(Int32,Set(Int32)))).new { |h,k|
   h[k] = Hash(Int32,Hash(Int32,Set(Int32))).new { |h,k|
      h[k] = Hash(Int32,Set(Int32)).new { |h,k|
         h[k] = Set(Int32).new
      }
   }
}

Now this clumsy way is the only to allow auto create of nested structures to be able write something like @update_tm_to_product_property[updated_at][group_id][product_id].add(property_id)

But look at type definition: Hash(Time,Hash(Int32,Hash(Int32,Set(Int32))))
We already have all exact information about the whole structure. Why not to allow auto vivification of nested structures is default behaviour for such types? Just to be able to write
@update_tm_to_product_property = Hash(Time,Hash(Int32,Hash(Int32,Set(Int32)))).new
and use it as I gave an example.

Why not ?

Most helpful comment

The defaults values for keys depends too much on the operations to be done afterwards: either T.new, T.zero, or 1 (if applied) might be useful default values.

I don't think it's an idiom so common to be in the std. Do you know any language that has that deep vivification?

Either way, you can refactor the code to reduce the type annotations:

h0 = Hash(Int32, Set(Int32)).new { |h, k| h[k] = Set(Int32).new }
h1 = Hash(Int32, typeof(h0)).new { |h, k| h[k] = h0 }
hash = Hash(Time, typeof(h1)).new { |h, k| h[k] = h1 }

t0 = Time.now
hash[t0][1][2] << 3
pp hash[t0][1][2]

Or even create a macro to do it for you

macro auto_vivify_hash(*types)
  %v{2} = Hash({{types[-2]}}, {{types[-1]}}).new { |h, k| h[k] = {{types[-1]}}.new }
  {% for i in 3..types.size %}
    %v{i} = Hash({{types[-i]}}, typeof(%v{i - 1})).new { |h, k| h[k] = %v{i - 1} }
  {% end %}
  %v{types.size}
end

hash = auto_vivify_hash(Time, Int32, Int32, Set(Int32))

t0 = Time.now
hash[t0][1][2] << 3
pp hash[t0][1][2]

All 5 comments

The defaults values for keys depends too much on the operations to be done afterwards: either T.new, T.zero, or 1 (if applied) might be useful default values.

I don't think it's an idiom so common to be in the std. Do you know any language that has that deep vivification?

Either way, you can refactor the code to reduce the type annotations:

h0 = Hash(Int32, Set(Int32)).new { |h, k| h[k] = Set(Int32).new }
h1 = Hash(Int32, typeof(h0)).new { |h, k| h[k] = h0 }
hash = Hash(Time, typeof(h1)).new { |h, k| h[k] = h1 }

t0 = Time.now
hash[t0][1][2] << 3
pp hash[t0][1][2]

Or even create a macro to do it for you

macro auto_vivify_hash(*types)
  %v{2} = Hash({{types[-2]}}, {{types[-1]}}).new { |h, k| h[k] = {{types[-1]}}.new }
  {% for i in 3..types.size %}
    %v{i} = Hash({{types[-i]}}, typeof(%v{i - 1})).new { |h, k| h[k] = %v{i - 1} }
  {% end %}
  %v{types.size}
end

hash = auto_vivify_hash(Time, Int32, Int32, Set(Int32))

t0 = Time.now
hash[t0][1][2] << 3
pp hash[t0][1][2]

@bcardiff wow, that's the first time I'm seeing %v{2} notation. Does {} denote anything else than part of the variable name?

%var ensure a fresh variable is used when expanding them. and the {index} sufix is a handy way to create temp variables across loop iterations for example and do tricky things 馃帺 馃悋 .

As usual, the first step is to code things in a plain way and get a pattern.

Auto vivification should work only for Structures, like Hashes and Arrays inside Hashes and Arrays when you use another level of subscription [] of [].
my_hash[k1][k2] - this should create some structure at my_hash[k1]

In perl5 and perl6 we definitely have autofification for Hashes and Arrays. In Perl there is 2 different ways of subscriptions: [] - for arrays, {} - for hashes. So it is always distinguishable what neccessary to vivify - array o hash:

my %h;
%h{"k1"}[5] = 6

will create array at %h{"k1"}

my %h;
%h{"k1"}{5} = 6

will create another hash at %h{"k1"}
It also works for deep subscriptions %h{"k1"}[5]{"k2"}[6]++

So in Crystal we also have enough information about which nested structure should be auto vivified because we have full type definition.

So this is special, not universal case - Not T.zero, 1 or something else possible when we want to have [] of it, and type definition point us that there is should be Array or Hash.

Of course it is possible to solve using macros. But the macro example you gave is quite difficult to write for average Crystal developer and, I think, should be part of standard lib.

Sometimes you have to write a bit more in Crystal, and that's fine.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rdp picture rdp  路  112Comments

MakeNowJust picture MakeNowJust  路  64Comments

malte-v picture malte-v  路  77Comments

asterite picture asterite  路  78Comments

akzhan picture akzhan  路  67Comments