The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

Hash::Map tutorial

Hint

When I write

    $obj = $obj->this_method;

I mean, that the Hash::Map object itself will be returned. So it is possible to build chains like that:

    $obj->this_method->next_method;

It is typical used for setter or worker methods.

Why existing both, method_name and method_name_ref?

The methods are existing as normal name and with postfix "_ref". The idea is that user code should be clear and free of noise like:

    $obj->name_ref( $hashref );
    $obj->name( %hash );
    # instaed of
    $obj->name( %{$hashref} );
    $obj->name_ref( \%hash );

    %hash     = $obj->target;
    $hash_ref = $obj->target_ref;
    # instead of
    %hash     = %{ $obj->target_ref };
    $hash_ref = { $obj->target };

OO style

OO style ist typical to use that module.

    require Hash::Map;

    # The constructor "new" is typical not called directly.
    # Methods "target", "set_target", "target_ref", "set_target_ref",
    # "source", "set_source", "source_ref", "set_source_ref"
    # and "combine" are alternative constructors.
    my $obj = Hash::Map->new;

    # set source hash
    $obj = $obj->source(b => 2, c => 3);
    $obj = $obj->set_source(b => 2, c => 3);
    $obj = $obj->source_ref({b => 2, c => 3});
    $obj = $obj->set_source_ref({b => 2, c => 3});

    # get source hash
    $source = $obj->source; # get, bacause no set parameters
    $source = $obj->source_ref;

    # set target hash
    $obj = $obj->target(a => 1);
    $obj = $obj->set_target(a => 1);
    $obj = $obj->target_ref({a => 1});
    $obj = $obj->set_target_ref({a => 1});

    # get target hash
    $target = $obj->target; # get, because no set parameters
    $target = $obj->target_ref;

    # combine - merge targets of other Hash::Map objects into $obj target
    $obj = $obj->combine(@objects);

    # clone source
    $obj = $obj->clone_source;

    # clone target
    $obj = $obj->clone_target;

    # delete keys in target
    $obj = $obj->delete_keys( qw(x y) );
    $obj = $obj->delete_keys_ref([ qw(x y) ]);

    # copy data from source to target using keys
    $obj = $obj->copy_keys(qw(b c))
    $obj = $obj->copy_keys_ref([ qw(b c) ]);
    # including a key rewrite rule as code reference
    $obj = $obj->copy_keys(
        qw(b c),
        sub {
            my $obj = shift;
            my $key = $_;
            return "new $key";
        },
    );
    $obj = $obj->copy_keys_ref(
        [ qw(b c) ],
        sub {
            my $obj = shift;
            my $key = $_;
            return "new $key";
        },
    );

    # copy data from source (key of map) to target (value of map)
    $obj = $obj->map_keys(b => 'bb', c => 'cc');
    $obj = $obj->map_keys_ref({b => 'bb', c => 'cc'});

    # merge the given hash into target hash
    $obj = $obj->merge_hash(d => 4, e => 5);
    $obj = $obj->merge_hashref({d => 4, e => 5});

    # modify target inplace by given code
    # Maybe the combined methods is what you are looking for,
    # see method "copy_modify_identical" or "map_modify_identical".
    $obj = $obj->modify(
        f => sub {
            my $obj = shift;
            my $current_value_of_key_f_in_target = $_;
            return; # $target{f} will be undef because of scalar context
        },
        ...
    );
    $obj = $obj->modify_ref({
        f => sub {
            my $obj   = shift;
            my $current_value_of_key_f_in_target = $_;
            return "new $value";
        },
        ...
    });

    # copy data from source to target using keys
    # and then
    # modify target inplace by given code
    # Maybe method "copy_modify_idientical" is what you are looking for.
    $obj = $obj->copy_modify(
        f => sub {
            my $obj = shift;
            my $current_value_of_key_f_in_target = $_;
            return; # $target{f} will be undef because of scalar context
        },
        ...
    );
    $obj = $obj->copy_modify_ref({
        f => sub {
            my $obj   = shift;
            my $current_value_of_key_f_in_target = $_;
            return "new $value";
        },
        ...
    });
    $obj = $obj->copy_modify_identical(
        qw(b c),
        sub {
            my $obj = shift;
            my $current_value_of_each_key_in_target = $_;
            return; # $target{key} will be undef because of scalar context
        },
    );
    $obj->copy_modify_identical_ref(
        [ qw(b c) ],
        sub {
            my $obj = shift;
            my $current_value_of_each_key_in_target = $_;
            return; # $target{key} will be undef because of scalar context
        },
    );

    # copy data from source (key of map) to target (value of map)
    # and then
    # modify target inplace by given code
    # Maybe method "map_modify_idientical" is what you are looking for.
    $obj = $obj->map_modify(
        f => ff => sub {
            my $obj = shift;
            my $current_value_of_key_f_in_source = $_;
            return; # $target{ff} will be undef because of scalar context
        },
        ...
    );
    $obj = $obj->map_modify_ref([
        f => ff => sub {
            my $obj   = shift;
            my $current_value_of_key_f_in_source = $_;
            return "new $value";
        },
        ...
    ]);
    $obj = $obj->map_modify_identical(
        (
            f => ff,
            ...
        ),
        sub {
            my $obj = shift;
            my $current_value_of_each_key_in_source = $_;
            return; # $target{key} will be undef because of scalar context
        },
    );
    $obj = $obj->map_modify_identical_ref(
        {
            f => ff,
            ...
        },
        sub {
            my $obj   = shift;
            my $current_value_of_each_key_in_source = $_;
            return "new $value";
        },
    );

Automatic construction

Methods "source", "set_source", "source_ref", "set_source_ref" "target", "set_target", "target_ref", "set_target_ref" and "combine" can work as constructor too.

    Hash::Map->new->target(...);
    Hash::Map->new->set_target(...);
    Hash::Map->new->target_ref(...);
    Hash::Map->new->set_target_ref(...);
    Hash::Map->new->source(...);
    Hash::Map->new->set_source(...);
    Hash::Map->new->source_ref(...);
    Hash::Map->new->set_source_ref(...);
    Hash::Map->new->combine(...);

shorter written as:

    Hash::Map->target(...);
    Hash::Map->set_target(...);
    Hash::Map->target_ref(...);
    Hash::Map->set_target_ref(...);
    Hash::Map->source(...);
    Hash::Map->set_source(...);
    Hash::Map->source_ref(...);
    Hash::Map->set_source_ref(...);
    Hash::Map->combine(...);

Functional style

The first idea was to implemnt that functional. But OO style is more clear readable. But this exists.

    use Hash::Map qw(hash_map hashref_map);

    %target_hash = hash_map(
        \%source_hash,
        # The following references are sorted anyway.
        # Running in order like written.
        [ qw(key1 key2) ],               # copy_keys from source to target hash
        [ qw(key3 key4), $code_ref ],    # copy_keys, code_ref to rename keys
        {
            source_key1 => 'target_key', # map_keys from source to target hash
            source_key2 => $code_ref,    # modify values in target hash
        },
    );

Similar, only the method name and return value has changed.

    $target_hashref = hashref_map(
        $source_hashref,
        ...
    );

Code example

Why are this code examples with Hash::Map longer than the original code?

Because this example is untypical or typical for fist development step. The fully code would explode this tutorial. If you have nearly 1 type of each mapping. Map it like before.

Often that copied code is not produced during first development. There are lots of changes and everyone adds lines. Then you have lots of nearly equal lines.

This module helps you to refractor, makes code readable and prevents: Don't repeat yourself.

Line reduced found code (maybe from fist development step)

    person_data(
        street       => $form->{street},
        city         => $form->{city},
        country_code => $form->{country_code} eq 'D'
                        ? 'DE'
                        : $form->{country_code},
        zip_code     => $form->{zip},
        name         => "$form->{first_name} $form->{family_name}",
        account      => $user->get_account,
        mail_name    => $mail->{name},
        mail_address => $mail->{address},
    );

Implemented with OO interface

    person_data(
        Hash::Map->combine(
            Hash::Map
                ->source_ref($form)
                ->copy_keys(
                    qw(street city)
                )
                ->copy_modify(
                    country_code => sub {
                        return $_ eq 'D' ? 'DE' : $_;
                    },
                )
                ->map_keys(
                    zip => 'zip_code',
                )
                ->merge_hash(
                    name => "$form->{first_name} $form->{family_name}",
                ),
            Hash::Map
                ->source_ref($user)
                ->copy_modify(
                    account => sub {
                        return $_->get_account;
                    },
                ),
            Hash::Map
                ->source_ref($mail)
                ->copy_keys(
                    qw(name address),
                    sub {
                        return "mail_$_";
                    },
                ),
        )->target
    );

Implemented with functional interface

    person_data(
        hash_map(
            # source_ref,
            $form,
            # copy_keys
            [ qw(street city country_code) ],
            {
                # modify
                country_code => sub {
                    return $_ eq 'D' ? 'DE' : $_;
                },
                # map_keys
                zip => 'zip_code',
            },
        ),
        # merge_hash
        name => "$form->{first_name} $form->{family_name}",
        hash_map(
            $user,
            # copy_keys
            [ qw(account) ],
            {
                # modify
                account => sub {
                    return $_->get_account;
                },
            },
        ),
        hash_map(
            $mail,
            [
                # copy_keys
                qw(name address),
                sub {
                    return "mail_$_";
                },
            ],
        ),
    );