Search code examples
perlhashcrystal-lang

Is there a way to implement statically-defined associative arrays in Perl 5?


If I have some hash with the (absolutely) static set of keys, can I avoid calculating of hash functions on each access to elements of this hash by key at run time?

Say, I have a static associative array %saa with the keys:

my %saa = (
 A => "aaa",
 B => "bbb",
 C => "ccc"
);

How to pre-calculate hash function values for these keys in compile time and use it in run time in such effective manner as I did when using indexed access to simple lists elements.

As I know for now, in perl5 it may be implemented only as emulation of hashes using lists (use constant A=>0, B=>1...; $saa[A]="ddd").

Maybe there is more straightforward way to implement static associative arrays (also called "named tuples", not hashes) in Perl ?

For example, that's how the desired feature was implemented in Crystal language.


Solution

  • The closest thing to the functionality described in struct NamedTuple(**T) is Const::Fast.

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    
    use Const::Fast qw( const );
    
    my %language;
    
    BEGIN {
        const %language => (
            name =>'Crystal',
            year => '2011',
        );
    }
    
    print "$language{$_}\n" for qw( name year other );
    
    $ perl t.pl
    Crystal
    2011
    Attempt to access disallowed key 'other' in a restricted hash at t.pl line 17.
    

    BUT the check happens at run time. See Neil Bowers' excellent review for performance information. The stats may have changed since then, but it should give you an idea.

    An alternative is not to use hashes at all, but use Class::XSAccessor to define a simple class:

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    
    package My::Language;
    use Class::XSAccessor (
        constructor => 'new',
        getters => {
            name => 'name',
            year => 'year',
        },
    );
    
    package main;
    
    my $language;
    
    BEGIN {
        $language = My::Language->new(
            name =>'Crystal',
            year => '2011',
        );
    }
    
    print $language->$_, "\n" for qw( name year other );
    

    Class::XSAccessor::Array should be about 10%-15% faster:

    #!/usr/bin/env perl
    
    use strict;
    use warnings;
    
    package My::Language;
    use Class::XSAccessor::Array (
        getters => {
            name => 0,
            year => 1,
        },
    );
    
    package main;
    
    my $language;
    
    BEGIN {
        $language = bless ['Crystal', '2011'] => 'My::Language';
    }
    
    print $language->$_, "\n" for qw( name year other );
    

    In all cases, the checks happen at run time.