http://qs1969.pair.com?node_id=11124918

Leudwinus has asked for the wisdom of the Perl Monks concerning the following question:

Fellow Monks,

How do you get the length of an array that is passed by reference to a subroutine?

In the program below, I pass the array @test_array which contains [1, 2, 3], [4, 5, 6] by reference to a subroutine test_function.

The expected result is 2 but I keep getting the error Can't use string ("1") as an ARRAY ref while "strict refs" in use at line 12.

use warnings; use strict; use feature qw/ say /; use Data::Dumper; $Data::Dumper::Indent = 0; sub test_function { my $ref_array = @_; say Dumper (@_); # $VAR1 = [[1,2,3],[4,5,6]]; say scalar @{ $ref_array }; # line 12 } my @test_array = ([1, 2, 3], [4, 5, 6]); &test_function(\@test_array);

Gratias tibi ago
Leudwinus

Replies are listed 'Best First'.
Re: Length of Array Passed as Reference
by jwkrahn (Abbot) on Dec 10, 2020 at 01:57 UTC

    Change:

    my $ref_array = @_;

    To:

    my ($ref_array) = @_;

    You are assigning the length of @_ to $ref_array, not the first element.

      Thank you for the quick reply!

Re: Length of Array Passed as Reference
by eyepopslikeamosquito (Archbishop) on Dec 10, 2020 at 03:23 UTC

    In addition to the excellent answers already given, I embed below a test program, for educational purposes, to show how to perform more thorough checking of function arguments. Such belt-and-braces checking of function arguments is probably over the top for most programs ... though there are times when you may feel it is warranted. Understanding the extra checks below should also help you better understand references in Perl.

    use strict; use warnings; use Data::Dumper; use Carp; # Note: you could do extra manual checking of function arguments. # Not necessarily recommended: shown for educational purposes. # In example below, test_function takes exactly one argument, a ref to + an ARRAY sub test_function { my $narg = @_; $narg == 1 or croak "oops: this function takes exactly one argumen +t, a ref to an array (you provided $narg arguments)"; my $ref = shift; defined($ref) or croak "oops: argument is undef"; my $reftype = ref($ref); $reftype eq '' and croak "oops: argument should be a reference"; $reftype eq 'ARRAY' or croak "oops: argument should be ARRAY ref ( +not a '$reftype' ref)"; warn "reftype='$reftype'\n"; # Note: next line will die with "Not an ARRAY reference error" if +$ref is not an ARRAY ref warn "num elts=", scalar(@$ref), "\n"; # ... function body, process array ref $ref ... print Dumper($ref); } my @test_array = ([1, 2, 3], [4, 5, 6]); my %test_hash = ( "one" => 1, "two" => 2 ); # Can test function being called with invalid arguments... # test_function(); # oops, no arguments # test_function(undef); # oops, passed undef # test_function( \@test_array, 2 ); # oops, two arguments # test_function(1); # oops, one argument but not a re +f # test_function( @test_array ); # oops, array (not array ref) # test_function( \%test_hash ); # oops, hash ref (not array ref) test_function( \@test_array ); # Correct! One argument, array re +f!

    Update: Minor improvements to argument checking, based on responses below; switched from die to Carp croak.

      Engaging beancounter mode ... - ready.

      sub test_function { my $ref = shift; # this function takes one argument, a ref to a +n ARRAY # Note: you could do extra manual checking of function arguments. # Not necessarily recommended: shown for educational purposes. defined($ref) or die "oops: you did not pass any arguments"; @_ and die "oops: this function takes exactly one argument";

      There is an edge case where this code produces a wrong error message:

      use strict; use warnings; use feature 'say'; sub test_function { my $ref = shift; # this function takes one argument, a ref to a +n ARRAY # Note: you could do extra manual checking of function arguments. # Not necessarily recommended: shown for educational purposes. defined($ref) or die "oops: you did not pass any arguments"; @_ and die "oops: this function takes exactly one argument"; } say "Calling test_function with no arguments:"; eval { test_function(); }; say $@; say "Calling test_function with a single undefined argument:"; eval { test_function(undef); }; say $@;
      X:\>perl foo.pl Calling test_function with no arguments: oops: you did not pass any arguments at foo.pl line 10. Calling test_function with a single undefined argument: oops: you did not pass any arguments at foo.pl line 10. X:\>

      Generally, the error messages are slightly confusing: They report the line where the error was detected, not where the error was made. Carp can help:

      use strict; use warnings; use feature 'say'; use Carp qw( croak ); sub test_function { @_==1 or croak "oops: this function takes exactly one argument"; my $ref = shift; # this function takes one argument, a ref to a +n ARRAY # Note: you could do extra manual checking of function arguments. # Not necessarily recommended: shown for educational purposes. defined($ref) or croak "oops: argument is not defined"; } say "Calling test_function with no arguments:"; eval { test_function(); }; say $@; say "Calling test_function with an undefined argument:"; eval { test_function(undef); }; say $@;
      X:\>perl foo.pl Calling test_function with no arguments: oops: this function takes exactly one argument at foo.pl line 7 main::test_function() called at foo.pl line 17 eval {...} called at foo.pl line 16 Calling test_function with an undefined argument: oops: argument is not defined at foo.pl line 12 main::test_function(undef) called at foo.pl line 23 eval {...} called at foo.pl line 22 X:\>

      Doing the same without the stack trace takes a little bit more work.

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)

        Yes, totally agree. I had a senior moment and forgot about Carp and its delightfully named cluck, croak and confess. Also forgot I responded to Best practices for handling errors (BTW, I see SunnyD snuck in a late reply to that old thread with a positive!!! node-rep - wouldn't happen today! :).

        I'll chuck in my customary reference to Conway's Perl Best Practices, Chapter 13 (Error Handling), where Damian wisely counsels you to "Have exceptions report from the caller's location, not from the place where they were thrown".

      Or:

      use feature 'state'; use Types::Standard -types; use Type::Params 'compile'; sub test_function { state $signature = compile( ArrayRef[Str] ); my ( $ref ) = &$signature; # function body goes here }

      The signature will check that the function was passed the correct number of arguments, the correct types, etc, and die if it wasn't.

        Nice. It seems to me your API embraces "Declarative trumps Imperative" (one of Conway's seven API design tips, mentioned at On Interfaces and APIs).

Re: Length of Array Passed as Reference
by GrandFather (Saint) on Dec 10, 2020 at 02:01 UTC

    The key issue is your parameter assignment in test_function. @_ is an array list so assignment in a scalar context will get you the length of the array list - in this case 1 because there is a single parameter passed to test_function. The fix is to use a list assignment:

    use warnings; use strict; use feature qw/say /; my @test_array = ([1, 2, 3], [4, 5, 6]); test_function(\@test_array); sub test_function { my ($ref_array) = @_; say scalar @$ref_array; }

    Prints:

    2

    Note too that in Perl since last century you don't need to call subs using &. In fact doing so can cause some very nasty to find bugs.

    Dumping $ref_array instead of @_ may have helped you pick up the error.

    Update: corrected brain fart - thanks haukex

    Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
      @_ is a list so assignment in a scalar context will get you the length of the list

      I think the distinction between list and array is important here. An array in scalar context returns its length, a literal list in scalar context returns its last element. @_ is an array.

      Thank you as well for the very quick response!

      The reason I did y $ref_array = @_ was because I was passing an array reference to the subroutine but didn't make the connection between the scalar assignment (which is what I did) and list assignment (which is what I should have done).

      I don't think I will ever post a non-reference-related question here based on my history but I swear, I am trying!

        I use the list assignment mantra as a matter of course when accessing parameters passed into a sub so anything else as the first line of the body of a sub looks strange to me. Doing the parameter assignments in a single hit up front makes it easier to see what should be passed into the sub. Getting into the habit of solving the same problem in the same way is often a Good Thing.

        Optimising for fewest key strokes only makes sense transmitting to Pluto or beyond
Re: Length of Array Passed as Reference
by karlgoethebier (Abbot) on Dec 10, 2020 at 19:32 UTC

    I don’t understand all this bohei. shift is your friend and scalar gives you the length. As you did...

    «The Crux of the Biscuit is the Apostrophe»

    perl -MCrypt::CBC -E 'say Crypt::CBC->new(-key=>'kgb',-cipher=>"Blowfish")->decrypt_hex($ENV{KARL});'Help