perl advanced perl skills 2

1. hash slice

hash slice can be used to retrieve hash members

my @three_scores = ($score{"barney"}, $score{"fred"}, $score{"dino"});
my @three_scores = @score{ qw/ barney fred dino / };

The slice must be a list, so the hash slice is also represented by the @ symbol

Why is the percentage sign (%) not used when hash is mentioned? Because the percent sign represents the entire hash, the hash slice (like other slices) is essentially a list rather than a hash. In perl, $represents a single thing, @ represents a group of things, and% represents an entire hash

my @players = qw/ barney fred dino /;
my @bowling_scores = (195, 205, 30);
@score{@players} = @bowling_scores;

What the last line does is equivalent to assigning a value to the ($score{"Barney"}, $score{"Fred"}, $score{"Dino"}) list with three elements

Hash slices can also be interpolated into strings

print "Tonight's players were: @players\n";
print "Their scores were: @score{@players}\n";

2. key value pair slice

perl5.20 began to introduce the concept of key value pair slicing, which can take out multiple key value pairs at a time

Previously, a set of values was extracted from hash slices, which was written as follows

my @values = @score{@players};

The @ used before the hash name indicates that we need to return a set of values, which are finally saved in the array @values. If you want to synchronously extract the corresponding keys, you need to pair them to generate a new hash

my %new_hash;
@new_hash{@players} = @values;

Or use the map function to pair

my %new_hash = map {$_ => $score{$_}} @players;
use v5.20;
my %new_hash = %score{@players};

Note that the symbol in front of the variable name here does not indicate the variable type. We just use it to specify the method of extracting data. The% here means to return in the form of hash key value pairs, so as to construct a new partitioned hash

Therefore, you can do this even for array variables, and return the array subscript as a hash key

my %first_last_scores = %bowling_scores[0, -1];

2. capture errors

1. use of Eval

The following typical error statements can crash the program

my $barney = $fred / $dino; # Divide by zero error?

my $wilma = '[abc'; # Illegal regular expression?
print "match\n" if /\A($wilma)/;

open my $caveman, '<', $fred # User supplied data error?
	or die "Can't open file '$fred' for input: $!";

How do you check the string $wilma to ensure that the regular expression it introduces is legal?
perl provides a simple way to catch serious errors that may occur when code is running, using eval blocks

eval( $barney = $fred / $dino);

Now even if $dino is 0, this line won't crash the program. As soon as eval finds a fatal error within its monitoring range, it will immediately stop running the entire block and continue to run the following code after exiting.
Notice that the eval block has a semicolon at the end. In fact, Eval is just an expression, not a control structure like while or foreach. Therefore, a semicolon must be written at the end of the monitored statement block

The return value of eval is the execution result of the last expression of the statement block, which is the same as that of the subroutine
Therefore, we can take the last $barney of the statement block from Eval and assign the running result of the eval expression to it. Thus, the declared $barney variable is located outside Eval for later use

my $barney = eval { $fred / $dino };

If eval catches an error, the entire statement returns undef. You can use definitions or operators to set default values for the final variables, such as NaN (meaning "Not a Number", Not a Number)

use v5.10;
my $barney = eval {$fred / $dino} // 'NaN';

When a fatal error occurs in the running eval block, only this statement will stop, and the whole program will not crash

When Eval ends, you need to judge whether everything is normal. If a fatal error is caught, Eval will return undef and set the error information in the special variable $@, for example: legal division by zero at my_ Program line 12 if no error occurs, $@ is empty. At this time, you can judge whether there is an error by checking whether the $@ value is true or false. Therefore, we often see such a piece of detection code immediately after the eval statement block

use v5.10;
my $barney = eval { $fred / $dino } // 'NaN';
print "I couldn't divide by \$dino: $@" if $@;

It can also be judged by checking the return value, as long as the true value can be returned during normal operation
But it's better to write like this

unless(defined eval { $fred / $dino }){
	print "I couldn't divide by \$dino: $@" if $@;
}

Sometimes, even if the part you want to test succeeds, there is no meaningful return value, so you need to construct another code block with a return value. If eval catches an error, it will not execute the last statement, that is, the single numeric expression 1

unless( eval { some_sub();1}){
	print "I couldn't divide by \$dino: $@" if $@;
}

In the list context, the captured eval will return an empty list
In the following line of code, if eval fails, @averages will only get two values in the end, because eval returns an empty list, which means that it does not exist

my @averages = ( 2/3, eval{ $fred / $dino }, 22/7 );

The eval statement is the same as other statements, so you can set the scope of variables in it

foreach my $person (qw/ fred wilma betty dino pebbles /){
	eval{
		open my $fh, '<', $person
			or die "Can't open file '$person': $!":
		
		my($total, $count);

		while(<$fh>){
			$total += $_;
			$count++;
		}

		my $average = $total/$count;
		print "Average for file $person was $average\n";

		&do_something($person, $average);
	};

	if($@){
		print "An error occurred($@), continuing\n";
	}
}

If an error occurs while processing a file in the list provided by foreach, you will get an error message, but the program will continue to execute the next file

The eval block can be written and nested in another Eval block. The inner Eval is responsible for capturing the errors in its own block and will not leak the errors to the outer block

The following code captures the divide by 0 error in the inner layer separately

foreach my $person (qw/fred wilma betty barney dino pebbles/){
	eval{
		open my $fh, '<', $person
			or die "Can't open file '$person': $!";
		
		my($total, $count);

		while(<$fh>){
			$total += $_;
			$count++;
		}
		my $average = eval{$total/$count} // 'NaN'# Inner eval
		print "Average for file $person was $average\n";

		&do_something($person, $average);
	};
	if($@){
		print "An error occurred($@), continuing\n";
	}
}

There are four types of errors that eval cannot capture

  1. Syntax errors in the source code, such as missing quotation marks, forgetting to write semicolons, missing operators, or illegal regular expressions
eval{
	print "There is a mismatched quote"\';
	my $sum = 42+;
	/[abc/;
	print "Final output\n"
};

The compiler of perl interpreter will catch such errors when parsing the source code and stop before running the program, while eval can only catch the errors that occur when perl runs

  1. An error that causes the perl interpreter itself to crash, such as a memory overflow or receiving a signal that it cannot take over. This kind of error will cause perl to terminate unexpectedly. Since perl has exited, it cannot be captured with eval
  2. eval cannot capture warnings, either from the user (the WARN function) or from perl itself. You can refer to the__ WARN__ Contents of pseudo signal
  3. (the last one is not actually an error) the exit operator will immediately terminate the program. Even if it is called in the eval block, the subroutine will immediately terminate

"eval string"
Program the strings directly as perl source code

my $operator = 'unlink';
eval "$operator \@files;";

2. more advanced error handling

Each language has its own way of handling errors, but most of them have a concept called exception. Specifically, it is to try to run a certain program, throw an exception if an error occurs, and then wait for the subsequent code responsible for handling such exceptions as catch to handle them accordingly

The most basic approach of perl is to throw an exception with die and then take over with eval. You can identify the error information stored in $@ to determine what the problem is

eval{
	...;
	die "An unexpected exception message" if $unexpected;
	die "Bad denominator" if $dino == 0;
	$barney = $fred / $dino;
};
if($@ =~ /unexpected/){
	...;
}elsif($@ =~ /denominator/){
	...;
}

However, there are many disadvantages. The most obvious thing is the dynamic scope of the $@ variable. Since $@ is a special variable, the written Eval may be included in another high-level eval. Therefore, it is necessary to ensure that the errors here are not confused with those in the high-level eval

{
local $@; # Not confused with high-level mistakes

eval{
	...;
	die "An unexpected exception message" if $unexpected;
	die "Bad denominator" if $dino ==0;
	%barney = $fred / $dino;
};
if($@=~ /unexpected/){
	...;
}elsif($@ =~ /denominator/){
	...;
}
}

Try:;Tiny module is very easy to use!!!

use Try::Tiny;

try{
	...; # Some code that may throw exceptions
}
catch{
	...; # Some code that handles exceptions
}
finally{
	...;
}

The function of try here is similar to the previous eval statement. Only when there is an error, the part in catch will be run, but finally the part in finally will be run

However, if catch or finally blocks can be omitted, you can only use the try statement and directly ignore the errors

my $barney = try {$fred / $dino};

You can use catch to handle errors. To avoid confusing $@, Try::Tiny puts the error information into the default variable$_ , but you can still access $@

use v5.10;

my $barney = 
	try {$fred / $dino}
	catch{
		say "Error was $_"; # No$@ 
	};
use v5.10;

my $barney =
	try {$fred / $dino}
	catch{
		say "Error was $_"; # No$@
	}
	finally{
		say @_ ? 'There was an error' : 'Everything worked';
	};

3. filter the list with grep

Select some members from the list

my @odd_numbers;

foreach (1..1000){ # Pick out odd numbers from a pile of numbers
	push @odd_numbers, $_ if $_ % 2;
}

my @odd_numbers = grep {$_ % 2 } 1..1000;

The first parameter of grep is the code block, where$_ Is a placeholder variable. The code block evaluates each element of the list and returns true or false values. The second parameter is the list of elements to be filtered
Extract the line containing fred from a file

my @matching_lines = grep { /\bfred\b/i } <$fh>;

my @matching_lines = grep /\bfred\b/i, <$fh>;

If the conditional judgment is only a simple expression, rather than the entire code block, it is OK to end the expression with a comma

The grep operator returns the number of elements that match the filter in the scalar context

my @matching_lines = grep /\bfred\b/i, <$fh>;
my $line_count = @matching_lines;

my $line_count = grep /\bfred\b/i, <$fh>;

4. use map to deform the list data

The following code formats the number as "amount number" output

# The following are traditional practices
my @data = (4.75, 1.5, 2, 1234, 6.9456, 12345678.9, 29.95);
my @formatted_data;

foreach(@data){
	push @formatted_data, big_money($_);
}


my @formatted_data = map {big_money($_)} @data;

The map does not return a logical true or false value, but the result of the actual evaluation of the expression
Moreover, the map statement block is evaluated in the list context, so more than one element can be returned at a time

Any form of grep or map statement can be rewritten into a foreach loop, but a temporary array is required to save data in the middle

print "The money numbers are:\n",
	map {sprintf("%25s\n", $_)} @formatted_data;

my @data = (4.75, 1.5, 2, 1234, 6.9456, 12345678.9, 29.95);
print "The money numbers are:\n",
	map {sprintf("%25s\n", big_money($_))} @data;

print "Some powers of two are:\n",
	map "\t" . (2**$_) . "\n", 0..15;

Tags: perl Unix programming language

Posted by jbille on Mon, 30 May 2022 06:43:50 +0530