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

You could call this part 2 of Function Prototype Context Conversion. Another bit of code which may not operate exactly as you would assume. This one is a little more challenging:
#!/usr/bin/perl -w use strict; sub foo($) { print "$_[0]\n"; } my @foo_bar = qw[ foo bar ]; sub returns_array { return @foo_bar; } sub returns_two { return qw[ foo bar ]; } sub returns_two_2 { return ('foo','bar'); } foreach ('foo(@foo_bar);', 'foo($foo_bar[0..$#foo_bar]);', 'foo(@foo_bar[0,$#foo_bar]);', 'foo("foo","bar");', 'foo(map{$_}"foo","bar");', 'foo(qw[ foo bar ]);', 'foo(@{[qw[ foo bar ]]});', 'foo(${[qw[ foo bar ]]}[0,1]);', 'foo(map{$_}qw[ foo bar ]);', 'foo(reverse @foo_bar);', 'foo(returns_two());', 'foo(returns_two_2());', 'foo(returns_array());') { printf("%-30s : ", $_); eval($_) || print "# $@\n"; }
There's some errors/warnings in there, just to keep things exciting.


The output of this program is hidden in this grey box:
foo(@foo_bar); : 2 Argument "" isn't numeric in array element at (eval 2) line 1. foo($foo_bar[0..$#foo_bar]); : foo foo(@foo_bar[0,$#foo_bar]); : bar foo("foo","bar"); : # Too many arguments for main::foo at + (eval 4) line 1, near ""bar")" foo(map{$_}"foo","bar"); : 2 Useless use of a constant in void context at (eval 6) line 1. foo(qw[ foo bar ]); : bar foo(@{[qw[ foo bar ]]}); : 2 foo(${[qw[ foo bar ]]}[0,1]); : bar foo(map{$_}qw[ foo bar ]); : 2 foo(reverse @foo_bar); : raboof foo(returns_two()); : bar foo(returns_two_2()); : bar foo(returns_array()); : 2
The question is, is there a simple method to convert or expand an array into a list? If you know the number of elements in advance, sure, this is simple, but what about the general case?

Replies are listed 'Best First'.
Re: Function Prototypes and Array vs. List (Pt. 2)
by demerphq (Chancellor) on Jun 13, 2002 at 15:35 UTC
    Hmm. Yet another set of examples of why prototypes cause more trouble than they save.

    When to use Prototypes?

    Yves / DeMerphq
    ---
    Writing a good benchmark isnt as easy as it might look.

Re: Function Prototypes and Array vs. List (Pt. 2)
by ariels (Curate) on Jun 13, 2002 at 14:09 UTC
    But you aren't "convert[ing] or expand[ing] an array into a list"; you're converting a list into a scalar. That's what the prototype means. It might not be what most people would want prototypes to mean, but that' still what the prototype means.

    How do you convert a list to a scalar? Easy. Meditate on this bit from perlfunc:

    Remember the following important rule: There is no rule that relates the behavior of an expression in list context to its behavior in scalar context, or vice versa.

    There is no general rule.

    You have to read the documentation for whatever it is you're using.

      The thing is, the documentation doesn't say. It would be something like this, although with a bit more gritty realism:
      create(id)

      Creates a Foo object with the id parameter set accordingly. id should be a valid record identifier.
      So, I have an array @bob = ('Bob') which I want to use on this function. Eyeballing it, it looks like it would fit, so I go ahead and use it:
      Foo::create(@bob);
      Do you get a warning? Nope. Yet later, you might notice that for some reason your Foo has an id of 1, which doesn't make any sense. Maybe smoke starts pouring out of your Perl program because of acalar conversion induced program failure. You lose a life and must hit "P1 Start" to continue.

      Without checking the source code you're never really going to know for sure.

      Conceputally, I was hoping for either a quick idiomatic way to do it, or something like one of these:
      Foo::create(listify(@bob)); # List converter? Foo::create($bob[0..$#bob]); # Array slice as list?
      Instead, you're going to have to do some work, which is anti-Lazy. In the general sense:
      Foo::bar($baz[0],$baz[1],$baz[2],...,$baz[$#baz]);


      Update:
      I certainly can't contest merlyn's "random crap" remark, so I'd say this was "premeditated ignorance". I knew the array had a single element and expected a particular behavior. After all, why expand twice (once on call, once on parameter import)? Well, sometimes you've just got to.
        See, I don't get this:
        create(id)

        Creates a Foo object with the id parameter set accordingly. id should be a valid record identifier.

        when put together with this:
        So, I have an array @bob = ('Bob') which I want to use on this function. Eyeballing it, it looks like it would fit
        What part of your brain says that when they wanted a "record identifier" (whatever that is, but it looks to be a single value), you can hand it an array?! That's the part that's off here. It's not Perl's fault you're trying to jam in something that doesn't make sense. And Perl has every right to given you surprising (to you) results when you've wandered out into unsuggested territory.

        As I saw somewhere in a purl factoid:

        You can't just throw random crap together and expect it to work.
        If I knew that the function was looking for a scalar ID, and I had that ID as the first element of an array I'd know immediately that I needed to hand it an element:
        create($bob[0])
        Case closed.

        -- Randal L. Schwartz, Perl hacker


        update: And if you can show me where you "Well, sometimes you've just got to." in actual useful code and not some junky little hypothetical misdesign, I'd be surprised.
        No, your hypothetical create method is simply wrong. It's buggy. It should be (to paraphrase John Cleese) a NON-method. It should be bereft of life and pushing up the daisies.

        The only reason to use prototypes is in order to give your functions the same syntax as various Perl builtins. E.g., with prototypes you can write a mymap that will have the same semantics as map. Your ``create'' method isn't doing that; it's using a prototype in order to create a bug.

        I could equally well claim that map is broken, because this code

        my %hash = map { $_ => X => 1 } qw(a b c d)
        does something very strange.

        You're right about coding, though: In order to make sure code does not have a bug, it is not enough to check documentation. You must look at the code.

        Why are you using a prototype? So far you've shown excellent reasons for you not to use a prototype for this function. ``Doctor, whenever I leave the spoon in the cup and drink my tea, my eye hurts!''

Re: Function Prototypes and Array vs. List (Pt. 2)
by Abigail-II (Bishop) on Jun 13, 2002 at 14:35 UTC
    The question is, is there a simple method to convert or expand an array into a list?
    Well, yes! The simplest way possible. As simple as converting a number to a string. If you want an array to act like a list, use it in list context!

    Abigail

      The problem is how adamant Perl is about backpropagating the wantarray = undef "scalar only, arrays will be converted automatically on sight" property of the function prototype. It just keeps going...and going...and going, all the way down the stack. In the example I posted, even reverse is joining the party, and it's a level removed!

      I didn't write the function that's prototyped, I just want to give data that happens to be in an array without fussing around too much, like expanding it out the hard way: $n[0],$n[1],...,$n[$#n]

      I don't disagree with your remark, I'm just astounded at how stringently prototypes are interpreted.
        But the reverse call isn't one level removed. The call to reverse is the first argument and it is being evaluated in scalar context. In your example you have only one case of using a literal list in the foo_bar() call, which gives you a prototype error. In all other cases you are calling foo_bar() with a single expression that will be evaluated according to the context of the first (or only in your case) prototype.