הלינקייה: מגזין חודשי למפתחים

רוצה לשמוע על כל האירועים, המדריכים, הקורסים והמאמרים שנכתבו החודש ?
הלינקייה הינו מגזין חופשי בעברית שמשאיר אותך בעניינים.
בלי ספאם. בלי שטויות. פעם בחודש אצלך בתיבה.

References

References Cheat Sheet

  Scalar ($) Array (@) Hash (%) Code (&)
Create Ref \$ \@ \% \&
Dereference $$ @$ %$ &$
Direct Access - $ref->[2] $ref->{key} $ref->(1, 3)
Anonymous - [2,3,4] { foo => bar } sub { ... }
ref SCALAR ARRAY HASH CODE

Perl References Explained

Perl references provide a way to treat complex data types (such as Hashes, Lists and Subroutines) as simple scalar. They allow us to pass these data types between subroutines and store them in other data types to build complex structures.
We'll start with some basics. In perl, we create a reference to an existing variable by prefixing it with a '\' character. So all of these are ways to create a new reference:

$scalarref = \$foo;
$arrayref  = \@ARGV;
$hashref   = \%ENV;
$coderef   = \&handler;
$globeref  = \*foo;

Note the return value of the \ operator is a scalar. All references in perl are scalar values (which is what makes them so useful).

Another way to create references in perl is the anonymouse reference syntax. Here, we create a completely new variable in perl, but we don't clutter the namespace with a real name for it (which is the reason it's called anonymous). Anonymous references are a great way to make our program more readable. Below we can see some examples for creating anonymous references.

The perl syntax is simple but may confuse. We use the [] operator to create an anonymous reference to a list, and the {} operator to create an anonymous reference to a hash, like the example below:

# We create anonymous arrays by using the [] operator
$arrayref = [1, 2, ['a', 'b', 'c']];
 
# Note the two lines below create the same list
@list = (\$a, \@b, \%c);
@list = \($a, @b, %c);
 
# We create anonymous hashes by using the {} operator
$hashref = { Adam => 'Eve', Clyde => 'Bonnie' };

After we have our references handy, let's see some examples of how to use them. In perl, the operation of using a reference is called "Dereferencing" it. Perl has three dereference operators, according to the three data types. Preceding a reference with a $ tells perl to take the scalar that this reference is referencing to, a @ tells perl to take the list this reference is refering to, and a % tells perl to dereference it as a dictionary.

It is important to get it right when dereferencing. Treating a reference the wrong way will lead to an exception. Here's a short example of dereferencing variables in perl:

use Modern::Perl;
 
my $x = 10;
 
# Taking a reference from a scalar
my $scalarref = \$x;
 
# Dereferencing it
$$scalarref = 7;
 
# Prints x = 7
say "x = $x";
 
# Dereferencing array refs
my $arrayref = ['a.txt', 'b.txt', 'c.txt'];
my $filename = 'd.txt';
push (@$arrayref, $filename);
 
# Dereferencing hash refs
my $hashref = { Adam => 'Eve', Bonnie => 'Clyde' };
$$hashref{Brad} = "Angelina";

References: The Good Parts

Now that we know how to use refererences, let's see what they can do for us. We'll start with an example of a complex data structure. As you may remember, perl keeps all its data in linear form, so we can only have one dimensional arrays. But, using references, we can work around this issue and build complex data structures, which are data structures whose elements are references to other data structures.

In the following example, we create an array of hash maps and use it.

use Modern::Perl;
 
my @aoh;
 
for (0..100) {
        $aoh[$_] = {
                Range => int($_ / 10),
                Value => $_
        }
}
 
print $aoh[54]{Range},"\n",$aoh[54]{Value},"\n";

And here we store subroutine references in a hash thus saving ourselves the evil if-else block

use strict;
use warnings;

sub foo {
    print "foo\n";
}

sub bar {
    print "bar\n";
}

sub hello {
    print "Hello: ", @_, "\n";
}

# The dispatch table is a hash containing
# function name and a reference to the 
# subroutine.
# It saves us the evil if-else block checking
# which function is needed.
# Here I used a hash ref, but a hash was just
# as good.

my $dispatch = {
    foo    => \&foo,
    bar    => \&bar,
    hello  => \&hello,
};

while (<>) {
    chomp;
    my ($cmd, @args) = split;
    if ( exists $dispatch->{$cmd} ) {
        # Note the dereferencing: 
        # It means: take the hashref $dispatch,
        #                find the value $cmd in that hash,
        #                treat it as a function ref, 
        #                call that function with @args
        $dispatch->{$cmd}->(@args);
    }
}

References - The Bad Parts

Having references handy means we need to start thinking about memory. Can you spot the bug in the program below ?

for $i (1..10) {
  @array = somefunc($i);
  $AoA[$i] = @array;
}

Since @array is not declared in this scope, we keep overwriting on its content. But, the references that we keep all refer to the same @array. In the end, each element in the list $AoA refers to points to the same list, that is @array.

To fix this we need to replace line 3 with code that copies the content of @array to a new list. An easy way to do that is:

$AoA[$i] = [ @array ];

Using ref to find out a reference type

Given the nature of perl, if we write a subroutine that accepts references - it is best we check that we actually got what we wanted.
Trying to treat a reference as a list when it is not one will lead to an error. Using the 'ref' keyword, we can write our own parameter validation blocks. When problems are found, we can use either warn or die to display the error.

use strict;
use warnings;

my $coderef = sub {
  print "Hello World\n";
}
 
my $lref = [1,2,3,4];
my $href = { a => 1, b => 2 };
 
$coderef->();
 
print 'coderef is of type: ', ref $coderef;
print 'lref    is of type: ', ref $lref;
print 'href    is of type: ', ref $href;
sub sum {   
  my $arrayref = shift;    
  warn "Not an array reference" if ref($arrayref) ne "ARRAY";   
  return eval join("+", @$arrayref);
} 

Merging Two Lists

Now that we have references, we can write a subroutine that takes two list refs and merges them, taking elements from both lists interchangeably.

use strict;
use warnings;
use Carp;
use List::Util qw(min);
use Data::Dumper;
 
sub merge {
    my ($lref, $gref) = map { [ @$_ ] } @_;
 
    my @merged = map {  shift @{ ($lref, $gref)[$_] }  }
        (0,1) x min (scalar @$lref, scalar @$gref);
 
    return (@merged, @$lref, @$gref);
}
 
my @l = (2, 4, 6, 8);
my @g = qw(a b c d);
 
my @merged = merge(\@l, \@g);
warn 'merged = ', Dumper( \@merged ); 

The Classic Game

Let's wrap things up with the classic rock/paper/scissors game implemented using subroutine refs

use Modern::Perl;
 
my @options = (\&rock, \&paper, \&scissors);
 
do {
    say "Rock, Paper, Scissors! Pick One: ";
    chomp( my $user = <STDIN> );
    my $computer_match = $options[ rand @options ];
    $computer_match->(lc ($user) );
 
} until (eof);
 
sub rock {
    print "I Chose Rock. ";
    given (shift) {
        when (/paper/)    { say 'You Win !'};
        when (/rock/)     { say 'We Tie !'};
        when (/scissors/) { say 'I Win !'};
        default           { say "I Don't Understand"};
    }
}
 
 
sub paper {
    print "I Chose Paper. ";
    given (shift) {
        when (/paper/)    { say 'We Tie !'};
        when (/rock/)     { say 'I Win !'};
        when (/scissors/) { say 'You Win !'};
        default           { say "I Don't Understand"};
    }
}
 
 
sub scissors {
    print "I Chose scissors. ";
    given (shift) {
        when (/paper/)    { say 'I Win !'};
        when (/rock/)     { say 'You Win !'};
        when (/scissors/) { say 'We Tie !'};
        default           { say "I Don't Understand"};
    }
}
 
course: