3

I defined hash and array constants, When passing those to a function, I'll have to pass them as references. However I'm wondering what the correct syntax is.

Consider this example:

#!/usr/bin/perl
use strict;
use warnings;

use constant AC => qw(a b c);

sub f($)
{
    print "ref=", ref $_[0], "\n";
    print "$_\n" foreach (@{$_[0]});
}

f(\AC);

When I run it I get:

ref=SCALAR
Use of uninitialized value $_ in concatenation (.) or string at /run/media/whatever/constref.pl line 10.

The Perl debugger prints AC as an array:

13: f(\AC);
  DB<1> x AC
0  'a'
1  'b'
2  'c'
  DB<2> c
U. Windl
  • 3,480
  • 26
  • 54

1 Answers1

4

The List Constants section in the constant pragma docs tells us that

Constants may be lists of more (or less) than one value.
...
List constants are lists, not arrays.

This means, among other properties, that one cannot take a reference of that "list constant" as if it were a single entity, like an array variable is; it behaves as a list, a group of scalars.

In order to accomplish what is asked then we need to build an (anonymous) array reference out of that list and pass that, f([AC])

use warnings;
use strict;
use feature 'say';

use constant AC => qw(a b c);

sub f {
    my ($r) = @_;
    say "ref=", ref $r;
    say for @$r;
}

f( [ AC ] );

This passes the "list constant" as a single value, an array reference, and it prints as expected. However, I don't like having to copy values, nor to further lose any semblance of constant-ness. There are other ways to do this but those are even less palatable to me.§

I'd suggest to reconsider the tool to use when proper read-only variables are needed.

There are other libraries for this and I'd recommend Const::Fast, or Readonly.

use Const::Fast;    
const my @const_ary => qw(a b c);
f( \@const_ary );                 # same f() from above

use Readonly;
Readonly my @carr => qw(a b c);
f( \@carr );                      # same f() from above

These are lexical variables that one can handle like any other. See docs.


Attempting to formally "take a reference" of a list results in a list of references

\($v, $t)  -->  \$v, \$t

While the AC itself is a constant, the list that it is associated with isn't read-only

use constant AC => qw(a b c);

(AC)[1] = "other";

say for AC;

prints

a
other
c

They're just not constant.


§ I can see two other ways

  • The constant pragma produces (is implemented as) a subroutine. Then one could use that and pass it as such, f(\&AC), and then use it as such, say for $r->().

    However, now we have to pass and dereference a subroutine off of that list symbol (AC), and get a list. This is a really bad hack.

  • The code in the question uses a "constant list." One can use a reference instead and that can be passed as such

    use constant AC => [ qw(a b c) ];
    
    # same sub f { } as above
    
    f( AC );  # prints as expected
    

    However, I don't see how to dereference AC to get the whole list (@{ AC } doesn't go?), apart from copying it to an arrayref first, like in f(). But then that defies the purpose of having it as a constant -- and all pretense to constant-ness is dropped.

zdim
  • 64,580
  • 5
  • 52
  • 81
  • On "*List constants are lists, not arrays*": So both are ordered, indexed and finite (which qualifies them as "array" for me), but the difference seems to be that (in C speak) that lists are no "lvalues", and references require an lvalue. However I've seen code like `my $r = \1;`. Trying that on a list seems to evaluate it as scalar, giving a reference to a scalar (which is the number of list items). Tricky, isn't it? – U. Windl Apr 11 '22 at 08:07
  • A great answer! As `Const::Fast` isn't shipped with my OS, I don't want to add another dependency. Unfortunately my OS also lacks the description for `Readonly` in Perl 5.18.2, even though it is mentioned in "SEE ALSO" of `man 3pm constant`. I guess it's a bug in the OS (SlES12 SP5). – U. Windl Apr 11 '22 at 08:15
  • I found out that I had to install a separate package (`perl-Readonly`) in SLES12 SP5. My previous guess was that it is included in the core package if available. – U. Windl Apr 11 '22 at 08:23
  • @U.Windl Heh, yes, it's tricky. The thing here is, a list isn't a variable; it is literally a list of scalars (on the stack), rather elusive and ephemeral. So "taking a reference" of _that_ doesn't really make sense -- and one gets a list with a reference of each. (Usually not what one meant :). But yes one can do `\1` etc. I'll look for some (of the many) posts which discuss list-vs-array tomorrow (it's late here now), it's quite interesting. – zdim Apr 11 '22 at 08:32
  • @U.Windl Glad if the answer helped! Good that you can do `Readonly`, it's much easier for use than `constant`, and "normal" (just variables). – zdim Apr 11 '22 at 08:36
  • @U.Windl On lists-vs-arrays -- here is [a post](https://stackoverflow.com/q/6023821/4653379) as some food for thought, with a few really nice posts. It turns out [a Perl FAQ](https://perldoc.perl.org/perlfaq4#What-is-the-difference-between-a-list-and-an-array) on this is good as well. In short, I'd say that lists are syntax features, ways to write/organize code, and a way for perl to move data (values on the stack). An array is a variable, data type/storage with a fixed (more or less) memory location. There are a lot of good statements on this, scattered around – zdim Apr 12 '22 at 16:36
  • 1
    @U.Windl They often behave similarly, and an array in expressions is (internally) often first evaluated into a list and then manipulated. But there are many differences in behavior as they are very different things. The case you bring up is a great example. With my `@ary = (2,3,4); my $r = \@ary;` that `$r` is, well, a reference to a variable (array). But with my `$r = \(2,3,4);` this goes: the reference-operator \ goes through the `()` (what else?) and, since the `=` operator is in scalar context, the comma operator evaluates one after another (with \ acting on each) ... and `$r` gets `\4` – zdim Apr 12 '22 at 18:58