9. Subroutine

Instead of typing the same commands in multiple places, you can define a reusable subroutine. Subroutines begin with the tag 'sub' and a unique descriptive label (the unique name of the subroutine - also called identifier), followed by a block between curly braces. Avoid a space between identifier and left curly brace. Explicit use of 'return' befor the closing curly brace is optional. In some cases makes 'return' clear what the output of the function is. Study the following examples.

9.1 Create

9.1.1 Fixed number of arguments
You do not define one or more parameters in the subroutine definition (1). The special array variable @_ is the list of incoming arguments to a subroutine. This means:

  • the first argument in it as $_[0]
  • the second argument in it as $_[1]
  • ...the nth argument in it as $_[n]
  • $#_ as the index number of the last argument
Instead of using $_[0] ... $#_ , you can better assign the arguments to a list. A few examples:

sub hello {
  ($name) = @_; # argument assigned to a one member list
  print("Hello $name\n");
}

hello("Reinier"); # output: Hello Reinier
Alternatively, you can use shift:

sub hello {
  $name = shift;
  print("Hello $name\n");
}

hello("Reinier"); # output: Hello Reinier
In case of multiple arguments, process @_ e.g. in a for loop:

sub hello_all {
  print("Hello $_\n") for @_;
}

hello_all( qw(Reinier Sebastian Daniel Florence) ); # output: Hello Reinier Hello Sebastian etc. on separate lines
Returning a calculated value:
sub add {
($x, $y) = @_; # argument assigned to a list
return ($x + $y); # the subroutine returns the sum of $x and $y, which is equal to $_[0] + $_[1]
}

$sum = add(2,3);
print($sum . "\n"); # output: 5
9.1.2 Unlimited number of arguments
What to do if you'll make a subroutine that accept an unlimited number of arguments? Simply iterate @_ with foreach.
sub add {
$sum = 0;
foreach $arg (@_) {
$sum += $arg;
}
return ($sum);
}

@list = (1, 2, 3, 4, 5);
$sum = add(@list); # notice $sum is another variable than the local one within the subroutine 'add'
print($sum . "\n"); # output: 15
Notice that it can be really inefficient to pass an array as argument. Later we'll discuss a better method.

The subroutine add can be more concise, while removing the variable and using the postfix foreach form:
sub add {
$sum = 0;
$sum += $_ foreach (@_);
return ($sum);
}

9.2 Access

9.2.1 Value of first and last arguments
Study the strategies to find the values of specific arguments in the next subroutine:
sub find_arg {
print("value first argument: " . $_[0] . "\n");
print("value first argument: " . shift . "\n");

print("value last argument: " . $_[$#_] . "\n");
print("value last argument: " . pop . "\n");
}

9.3 Print

sub multiply {
($x, $y) = @_;
return ($x * $y);
}

print(multiply(2,3) . "\n");

9.4 Iterate

Not spectacular, but subroutines can be used within a loop.
sub power {
($x, $y) = @_;
return ($x ** $y);
}

for ($i = 0;$i < 10; $i++) {
print($i . " " . power(2,$i) . "\n");
}

9.5 Operate

9.5.1 Return multiple values
Of course, a subroutine can return multiple values, thanks to the list.
sub calculate {
($x, $y) = @_;
return ($x + $y, $x * $y);
}

($sum, $mult) = calculate(2, 3);
print("Sum: " . $sum . "\n");
print("Multiplication: " . $mult . "\n");
9.5.2 Default value
Use the || operator to define a default value, here 'red'.
sub favorite_color {
    return $_[0] || "red";
}

print(favorite_color("green") . "\n"); # prints: green
print(favorite_color() . "\n"); # prints: red
Another example:
sub sum_of_powers {
    my ($end, $power, $start) = @_;
    $power || ($power = 2); # or: $power ||= 2;
    $start ||= 1;

    my $sum = 0;
    for my $i ($start .. $end) {
        $sum += $i ** $power;
    }
    return($sum);
}

print(sum_of_powers(10), "\n"; # 385
print(sum_of_powers(10, 3), "\n"; # 3025
print(sum_of_powers(10, 3, 5), "\n"; # 2925
9.5.3 Be careful with @_
The call by reference method of passing arguments means that not the values of variables will be passed, but the references to the variables (i.e. the address of the variables). So, via @_ you can change the values of variables directly. Study the following:
sub modify {
$_[0] = 3;
$_[1] = 4;
return ($_[0], $_[1]);
}

$var1 = 1;
$var2 = 2;
modify($var1, $var2);
print($var1 . " " . $var2 . "\n"); # prints: 3 4
There is another thing:
($a, $b, @c) = @_
will work. However, the following gives an error, because @c is 'greedy' and $a and $b do not have assigned any value:
(@c, $a, $b) = @_

10. Arguments: how-to


1. How to use shift or shift()?

a. To remove the first element of an array:

@arr = (1,2,3);
print(shift(@arr));# output: 1
print("@arr");# output: 2 3
b. To get and remove the first argument of @_ in a function:
sub greeting
{
  print("Hello " . shift); # or shift(@_)
}
greeting("Reinier", "Daniel");# output: Hello Reinier
c. To get and remove the first argument of @ARGV in file scope:
# contents of test.pl:
print(shift);

# invoke this test.pl with e.g. two arguments
test.pl arg1 arg2

# output: arg1
2. How to handle many arguments in a subroutine?

The question is about readibility of a function. The following is for me not clear.
image_link ("https://www.reiniermaliepaard.nl", 0, "at home", "img/at_home.jpeg", 600, 452);	
The best solution is "named arguments" using a hash reference. Study this code:
sub image_link {

  ($args) = @_;
  
  return $img_link = '<a href = '$args->{href}'><img ' .
                     'border = '$args->{img_border}' alt = '$args->{img_alt} ' .  
                     'src = '$args->{img_src}' width= '$args->{img_width} ' .
                     'height = '$args->{img_height}'></a>';

} 

$img_link_def = image_link ({
                               href       => 'https://www.reiniermaliepaard.nl',
                               img_border => 0,
                               img_alt    => 'at home',
                               img_src    => 'img/at_home.jpeg',
                               img_width  => 600,
                               img_height => 452,	
                            });

print($img_link_def);
# <a href = "https://www.reiniermaliepaard.nl"><img border = "0" alt = "at home" src = "img/at_home.jpeg" width= "600" height = "452"></a>
3. How to check/validate arguments (function/command line)?
The answer has to do with processing the special array variables @_ (functions) and @ARGV (command line). In addition, there are Modules which can help you.

a. Check if there are arguments: evaluate the special variables in scalar context.
if (! @ARGV) {
    print "No command-line arguments provided.\n";
}	

if (! @_) {
    print "No function arguments provided.\n";
}
b. Check the numbers arguments: evaluate the special variables in scalar context and compare.
if (@ARGV != 2){ 
    die "Expected two arguments: @ARGV";
}

if (@_ != 3){ 
    die "Expected three arguments: @_;
}
c. Check if the arguments (command line) meet certain criteria: same type.
if (@ARGV) {
	for(@ARGV){ 
		die "Error: expected only positive integers: $_" unless /^\d+$/;
	}
}	

# the next code saved as add_no.pl
#!/usr/bin/perl

sub add_2_numbers {
	if (@_ == 2) {
		for(@_){ 
			die "Error: expected only positive integers: @_" unless /^\d+$/;
		}
	}
	else {
		die "Error: expected two positive integers: @_";	
	}	
}

add_2_numbers (1, -2);# Error: expected two positive integers: -2 at add_no.pl line 7.

add_2_numbers (1, -2, 3);# Error: expected two positive integers: 1 -2 3 at add_no.pl line 12.
d. Check if the arguments (command line) meet certain criteria: different type.
#!/usr/bin/perl

# Check if the correct number of arguments is provided
if (@ARGV != 2) {
    die("Usage: $0  \n");
}

# Capture command-line arguments
($arg1, $arg2) = @ARGV;

# Validate argument 1 (e.g., it should be a positive integer)
if ($arg1 !~ /^\d+$/ || $arg1 <= 0) {
    die("Argument 1 must be a positive integer.\n");
}

# Validate argument 2 (e.g., it should be a string)
if ($arg2 !~ /^[A-Za-z]+$/) {
    die("Argument 2 must contain only letters.\n");
}

# If all validations pass, proceed with your script logic
print "Argument 1: $arg1\n";
print "Argument 2: $arg2\n";

# Your application logic goes here
e. Params::ValidationCompiler.
Params::ValidationCompiler is a Perl module that provides a convenient way to validate and sanitize input arguments for subroutines or functions. It allows you to define validation rules for each parameter and provides a structured and maintainable approach to parameter validation. However, stricter validation can be necessary.

Exit: remove leading zeros from positive numbers (Perl Weekly Challenge 002)

sub str2int {
  $arg = shift;
  print($arg," : ", ($arg + 0), "\n");	
}	

$str = "00012345"; # 12345
str2int($str);
$str = "+0678"; # 678
str2int($str);
$str = "0000090.123"; # 90.123
str2int($str);
$str = "+00456.789"; # 456.789
str2int($str);
$str = "0.123"; # 0.123
str2int($str);
$str = "000.123"; # 0.123
str2int($str);
$str = ".123"; # 0.123
str2int($str);

Footnotes
(1) I skip the case that a subroutine is called without one or more arguments.