1

I'm just starting out Perl (about 15 minutes ago) using a tutorial online. I made a small subroutine to test a few Perl features and would like to know if it is possible to determine at runtime if parameters that were passed to the sub' call are arrays or scalars. Let's use the sub I wrote as an example:

#!/usr/bin/perl

sub somme_prod {
    if (scalar(@_) > 1) {
        $facteur = shift(@_);
        foreach my $nb (@_) {
            $resultat += $nb
        }
        return ($resultat * $facteur);
    }
    else {
        return "ERREUR";
    }
}

print somme_prod(2, 2, 3, 7);

This is a basic sum-product subroutine which does exactly what its name says. Now, would it be possible to modify this subroutine to allow for a mix of arrays and scalars like this ?

somme_prod(2, (2,3), 7);
somme_prod(2, (2,3,7));
#...

Also, any comment on the style of Perl coding demonstrated here is much welcome. I have a background of amateur C++ coding so I may not be thinking in Perl.

Edit: I'm so sorry. I actually tried it after posting and it seems that Perl does process my sub as I want it to. Now I guess my question would be more "how does Perl know how to process this" ?

Edited code for a more Perl-ish version.

ApplePie
  • 8,814
  • 5
  • 39
  • 60
  • Just a note on style, in Perl it's much nicer to say `foreach my $thing( @things ) { ... }` to loop over arrays. – friedo Aug 12 '12 at 21:03
  • That was my initial idea but that made it difficult to differentiate the values to be summed and the factor (which would always be the first value), unless there is a trick I am not aware of. – ApplePie Aug 12 '12 at 21:05
  • I've found that there is a variation of foreach that turns the variable into an index effectively creating a loop that is shorter and easier to read: foreach my $i (1 .. $#_) – ApplePie Aug 12 '12 at 21:30
  • Just for clarification, only a list of scalars can be passed to and from Perl subroutines; arrays cannot be passed. See [ikegami's](http://stackoverflow.com/users/589924/ikegami) discussion in [Passing arrays to functions in perl](http://stackoverflow.com/questions/7237729/passing-arrays-to-functions-in-perl). – Kenosis Aug 12 '12 at 21:33

3 Answers3

2

Yes; in Perl you can create references to arrays (or hashes, or anything else) to stuff several values into a single parameter.

For example:

somme_prod(2, [2, 3], 7);

...would resolve to:

sub somme_prod {
  foreach my $arg (@_) {
    if (ref($arg) eq 'ARRAY') {
      my @values = @$arg; # dereference, e.g. [2, 3] -> (2, 3)
      . . .
    } else {
      # single value, e.g. "2" or "7"
    }
  }
}

You can read the page perldoc perlref to learn all about references.

Kevin Grant
  • 5,363
  • 1
  • 21
  • 24
  • Nevermind my comment (if you read it before I removed it). Thanks for the explanation ! :) – ApplePie Aug 12 '12 at 20:57
  • 2
    It is *very wrong* to use prototypes on a subroutine so don't do it. Here your prototype insists that the subroutine has no parameters so it stops it working at all! – Borodin Aug 12 '12 at 22:04
  • Minor typo. Yes, "no parentheses" or "(@)" would be a valid prototype. But also not the main purpose of the example. – Kevin Grant Aug 12 '12 at 22:53
  • @KevinGrant: No, you should *never* use prototypes unless you are writing a replacement for a Perl built-in operator. – Borodin Aug 12 '12 at 23:18
2

Perl handles lists and arrays differently, and a useful document for you to read is What is the difference between a list and an array?

Perl will always flatten nested lists (and so arrays within lists) so

my @data1 = (2, (2, 3), 7);

or

my @data2 = (2, 3);
my @data1 = (2, @data2, 7);

is equivalent to

my @data1 = (2, 2, 3, 7);

As Kevin says, if you want nested arrays you have to place an array reference in the place where the sublist appears. Because a reference is a scalar it won't get expanded.

Your subroutine is fine, but using some de-facto standards would help others to follow your program. Firstly the convention is that a subroutine will return undef if there is an error, so that you can write

sous_routine($p1, $p2) or die "Erreur";

In this case the possibility that zero is a valid result spoils this, but it is still best to stick to the rules. A plain return without a parameter indicates an error

A little bit of tidying up and using unless and if as statement modifiers gives this

sub somme_prod {
  return unless @_ > 1;
  my $facteur = shift;
  my $somme = 0;
  $somme += $_ for @_;
  return $somme * $facteur;
}

print somme_prod(2, 2, 3, 7);
daxim
  • 39,270
  • 4
  • 65
  • 132
Borodin
  • 126,100
  • 9
  • 70
  • 144
0

You've known Perl for 15 minutes? Forget about references for now.

Basically, everything passed to a subroutine is an array. In fact, it's stored in an array called @_.

# /usr/bin/env perl

use strict;   #ALWAYS USE!
use warnings; #ALWAYS USE!

my @array = qw(member1 member2 member3 member4);

foo(@array, 'scalar', 'scalar', 'scalar');

sub foo {

print "My input is " . join (":", @_) . "\n";

This will print:

my input is member1:member2:member3:member4:scalar:scalar:scalar

There is no way to tell which entries are from an array and which are from a scalar. As far as your subroutine is concerned, they're all members of the array @_.


By the way, Perl comes with a command called perldoc. When someone says to see perlref, you can type in perldoc perlref at the command line and see the documentation. You can also go to the site http://perldoc.perl.org which will also contain the same information found in the perldoc command.


Now about references....

A data element of a Perl array or the value of a hash can only contain a single value. That could be a string, it could be a real number, it could be an integer, and it could be a reference to another Perl data structure. That's where all the fun and money is.

For example, the same subroutine foo above could have taken this information:

foo(\@array, 'scalar', 'scalar', 'scalar'); #Note the backslash!

In this case, you're not passing in the values of @array into foo. Instead, a reference to the array is passed as the first data element of @_. If you attempted to print out $_[0], you'd get something like ARRAY:6E43832 which says the data element is an array and where it's located in memory.

Now, you can use the ref function to see whether an piece of data is a reference and the type of reference it is:

 sub foo {
     foreach my $item (@_) {

        if (ref $item eq 'ARRAY') {
           print "This element is a reference to an array\n";
        }
        elsif (ref $item eq 'HASH') {
           print "This element is a reference to a hash\n";
        }
        elsif (ref $item) {   #Mysterious Moe Reference
            print "This element is a reference to a " . lc (ref $item) . "\n";
        }
        else {
            print "This element is a scalar and it's value is '$item'\n";
        }
    }
}

Of course, your reference to an array might be an array that contains references to hashes that contain references to arrays and so on. There's a module that comes with Perl called Data::Dumper (you can use perldoc to see information about it) that will print out the entire data structure.

This is how object orient Perl works, so it's really quite common to have references to other Perl data structures embedded in a piece of Perl data.

Right now, just get use to basic Perl and how it works. Then, start looking at the various tutorials about Perl references in Perldoc.

David W.
  • 105,218
  • 39
  • 216
  • 337
  • Yes, 15 minutes (now a few hours I guess). I read a few "cheat sheets" and "quick references" and then a more thorough very basic tutorial of the main features (loops, variables, arrays, hashes...). Oh and thanks for the added info. Will look into those links as well :) – ApplePie Aug 12 '12 at 23:49