http://qs321.pair.com?node_id=11116960


in reply to Converting Hashes to Objects

Inspiration whacked me in the head yesterday, and I realized that it was pretty easy to take an arbitrary hash reference and turn it into an object, so that instead of writing $hash->{key} I can just write $hash->key, saving two characters and giving me protection from typos. Enter Util::H2O:

I'm glad that you took the time and effort to create this software tool, as it seems to have gotten me unstuck on a line of code and with sitting on the fence in general. I know that meditations are typically where a person doesn't come to ask questions, but I think a meditation gets bonus points when it solves a problem for someone else, that being me today. I like to conserve vertical space for the collective scrollfingers of the monastery so present output, source, and questions in readmore tags:

I was trying to create a main data structure using a hash and ran aground in Re^6: implementing a scrabble-esque game on Termux III and how best to create a main data structure. At the time I wrote:

So I'm mystified that  $vars{max_tiles} = 7; does not initialize, even demoralized. I had wanted to create a central data structure, and thought a hash would be the ticket. Let me ask this: Would I have an easier time housing and calling this data if I created an object out of it? Right now, I have to pass a reference and receive it as well; I think it's hideous: $ref_var = init_vars($ref_var); How would $vars{max_tiles} look like if it were blessed into a class?

Well, I found out today. The install goes well

$ scm Util::H2O [sudo] password for hogan: --> Working on Util::H2O Fetching http://www.cpan.org/authors/id/H/HA/HAUKEX/Util-H2O-0.06.tar. +gz ... OK Configuring Util-H2O-0.06 ... OK Building and testing Util-H2O-0.06 ... OK Successfully installed Util-H2O-0.06 1 distribution installed $

Output:

$ ./2.3.up.pl abs is /home/hogan/Documents/hogan/2.3.up.pl path1 is /home/hogan/Documents/hogan path2 is /home/hogan/Documents/hogan/games This script will build the above path2. Proceed? (y|n)y exiting init 7 survived to read this Can't locate object method "x" via package "Util::H2O::_5620e865a5b8" +at ./2.3.up.pl line 36. $

Source:

#!/usr/bin/perl use strict; # multiplayer upwords use warnings; use 5.016; use Path::Tiny; use Data::Dump; ## paths and constraints @ARGV or @ARGV = qw( one two three four ); # for testing my $abs = path(__FILE__)->absolute; my $path1 = Path::Tiny->cwd; my %var; #main data structure my $ref_var = \%var; $var{abs} = $abs; $var{cwd} = $path1; $ref_var = init_vars($ref_var); my ( $board, $heights ) = create_board($ref_var); my @dictwords = create_dict($ref_var); my @drawpile = create_draw_pile(); ### re-wire for H2O use Util::H2O; my $hash = h2o $ref_var; print $hash->max_tiles, "\n"; #$var{max_tiles} * @ARGV > @drawpile and die "too many players for ti +les\n"; # Can I get past this line? $hash->max_tiles* @ARGV > @drawpile and die "too many players for til +es\n"; say "survived to read this"; #$hash->x("z"); # setter dd $hash; sub x { my $value = shift; say "$value"; } sub init_vars { use Path::Tiny; use Data::Dump; use 5.016; use warnings; use POSIX qw(strftime); my $rvars = shift; my %vars = %$rvars; my $script = $vars{abs}; my $path1 = $vars{cwd}; my $games = "games"; my $path2 = path( $path1, $games ); say "abs is $abs"; say "path1 is $path1"; say "path2 is $path2"; print "This script will build the above path2. Proceed? (y|n)"; my $prompt = <STDIN>; chomp $prompt; die unless ( $prompt eq "y" ); $vars{side} = 9; $vars{d_file} = path( "my_data", 'enable1.txt' ); $vars{ca_name} = path( "my_data", "words.11108138.$vars{side}" ); my $munge = strftime( "%d-%m-%Y-%H-%M-%S", localtime ); $munge .= ".txt"; $vars{save_file} = path( $path2, $munge )->touchpath; $vars{save_file}->append_utf8("Script executing is $script\n"); # other initializations $vars{max_tiles} = 7; $vars{n_1} = $vars{side} + 1; $rvars = \%vars; #dd $rvars; say "exiting init"; return ($rvars); } sub create_board { use warnings; use 5.016; my $rvars = shift; my %vars = %$rvars; my $board = ( '.' x $vars{side} . "\n" ) x $vars{side}; my $heights = $board =~ tr/./0/r; return ( $board, $heights ); } sub create_dict { my $rvars = shift; my %vars = %$rvars; use strict; # was 7.word.pl before becoming subroutine use warnings; use Path::Tiny; use Data::Dump; use 5.016; use POSIX qw(strftime); my $cachefilename = path( "my_data", "upwords." . $vars{n_1} ); my $download_file = '/home/hogan/Documents/hogan/my_data/1.english.t +xt'; unless ( -s $cachefilename && -s $download_file ) { use LWP::Simple; my $url = 'https://storage.googleapis.com/google-code-archive-downloads/v2/code. +google.com/dotnetperls-controls/enable1.txt'; say "execution was here"; getstore( $url, $download_file ); } ## substitute q for qu my $dictionaryfile = path($download_file); my @dictwords; if ( -s $cachefilename ) { @dictwords = path($cachefilename)->lines( { chomp => 1 } ); } unless ( -s $cachefilename ) { print "sub q for qu"; @dictwords = path($dictionaryfile)->lines( { chomp => 1 } ); s/qu/q/g for @dictwords; # some words shortened # cache the ones of maxtiles length @dictwords = grep( /^[a-z]{2,$vars{n_1}}\z/, @dictwords ); $cachefilename->spew( join "\n", @dictwords, '' ); } my $refDict = \@dictwords; return @dictwords; } sub create_draw_pile { use List::Util qw( shuffle ); use warnings; use 5.016; my @drawpile = shuffle + # thanks to GrandFather 11108145 ('a') x 7, ('b') x 3, ('c') x 4, ('d') x 5, ('e') x 8, ('f') x 3, ('g') x 3, ('h') x 3, ('i') x 7, ('j') x 1, ('k') x 2, ('l') x 5, +('m') x 5, ('n') x 5, ('o') x 7, ('p') x 3, ('q') x 1, ('r') x 5, ('s') x 6, +('t') x 5, ('u') x 5, ('v') x 2, ('w') x 2, ('x') x 1, ('y') x 2, ('z') x 1; return @drawpile; } __END__

The big win here is getting this line:

$var{max_tiles} * @ARGV  > @drawpile and die "too many players for tiles\n";

from one that complained of unitialized values to

$hash->max_tiles* @ARGV  > @drawpile and die "too many players for tiles\n";

so we finally pass this sanity check.

I tried to understand

$hash->x("z");  # setter

by writing the following

sub x { my $value = shift; say "$value"; }

, but this doesn't help the syntax not be an error so far.

Q1) How is a "setter" to be implemented?

Q2) Do I still have to pass this around as clumsily as I am with these hash references now. How do I edit out as much of that chaff as I can?

Q3) Is there now a better way to do this?

my $abs = path(__FILE__)->absolute; my $path1 = Path::Tiny->cwd; my %var; #main data structure my $ref_var = \%var; $var{abs} = $abs; $var{cwd} = $path1;

Again, thx, haukex, for helping me off a dime.

Replies are listed 'Best First'.
Re^2: Converting Hashes to Objects
by haukex (Archbishop) on May 20, 2020 at 08:11 UTC
    my %vars  = %$rvars;

    A note on this: When you do this, you're creating a (shallow) copy of the hash, and if $rvars happens to be a Util::H2O object, you'd lose its methods. If that's not what you want, you'll have to work with the hash reference directly. In newer versions of Perl (>=5.22), there is an experimental feature that allows aliasing:

    use experimental 'refaliasing'; my $rvars = { foo=>"bar" }; \my %vars = $rvars; # alias print $vars{foo}, "\n"; # prints "bar" $vars{abc} = "xyz"; # modifies $hashref's contents

    But since it's experimental, it's probably better to stick with $rvars->{foo} instead.

    Q1) How is a "setter" to be implemented?

    In the context of Util::H2O, getters/setters are created for every key that exists in the hash or is given as an "additional key" at the time of the h2o call. So for your $hash->x("z") to work, there'd need to be a key x in the hash or you need to specify it to the h2o function; you don't need to write your own sub x.

    Q2) Do I still have to pass this around as clumsily as I am with these hash references now.

    Q3) Is there now a better way to do this?

    TIMTOWTDI, here's how I might have written it without the module:

    use Path::Tiny; use Time::Piece; my $ref_var = { abs => path(__FILE__)->absolute, cwd => Path::Tiny->cwd, }; init_vars($ref_var); print $ref_var->{save_file}, "\n"; sub init_vars { my $rvars = shift; # ... $rvars->{save_file} = path( $rvars->{cwd}, "games", localtime->strftime("%d-%m-%Y-%H-%M-%S.txt") )->touchpath; # ... }

    And with the module, one can write:

    use Path::Tiny; use Time::Piece; use Util::H2O; my $ref_var = h2o { abs => path(__FILE__)->absolute, cwd => Path::Tiny->cwd, }, qw/ save_file ... /; init_vars($ref_var); print $ref_var->save_file, "\n"; sub init_vars { my $rvars = shift; # ... $rvars->save_file( path( $rvars->cwd, "games", localtime->strftime("%d-%m-%Y-%H-%M-%S.txt") )->touchpath ); # ... }