http://www.perlmonks.org?node_id=228837

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

Can anyone explain why the line

map2{ print "$a $b"; } qw[A B C D];

would produce the error

Type of arg 1 to main::map2 must be block or sub {} (not list) at C:\t +est\test.pl line 17, near "qw[A B C D];"

The error implies that it has a problem with argument 1 to the sub, but this is manifestly wrong as swaping the qw[..] for an array with the same contents and it works fine.

I'm guessing that this is something to do with the prototype, which I know can be considered evil, but there is no other way to get the map style syntax which we all love.

#! perl -slw use strict; sub map2 (&@) { use Carp; my $code = shift; croak 'Odd number of values in list' if @_ & 1; map { local ($a, $b) = (shift,shift); $code->() } 1 .. (@_>>1); } #! This works. my @array = ('A'..'J'); map2{ print "$a $b"; } @array; =pod This code gives compile time error. map2{ print "$a $b"; } qw[A B C D]; C:\test>test Type of arg 1 to main::map2 must be block or sub {} (not list) at C:\t +est\test.pl line 17, near "qw[A B C D];" Execution of C:\test\test.pl aborted due to compilation errors. =cut #! But this works fine?? map{ print $_; } qw[A B C D];

Examine what is said, not who speaks.

The 7th Rule of perl club is -- pearl clubs are easily damaged. Use a diamond club instead.

Replies are listed 'Best First'.
Re: Problem emulating built-ins like map and grep.
by Enlil (Parson) on Jan 21, 2003 at 21:44 UTC
    For what it is worth your sub works both ways using Perl 5.8. I tried it using the activestate distribution: and also using Perl 5.8 (built from source on a SCO OSR5.5): update:I have just tried it on a RH Linux Box and as expected works one way and not the other (Perl 5.6.1).

    Hope this helps.

    -enlil

      Thanks Enlil. Time to upgrade I guess, but I thought I heard there were problems with the current AS distribution, and my attempts to build perl have ground to a halt so far.


      Examine what is said, not who speaks.

      The 7th Rule of perl club is -- pearl clubs are easily damaged. Use a diamond club instead.

Re: Problem emulating built-ins like map and grep.
by jsprat (Curate) on Jan 21, 2003 at 22:28 UTC

    It looks like a bug to me, somehow related to qw, not the prototype. You can supply a list as an argument, just not one generated by qw.

    map2{ print $a $b; } 'A', 'B', 'C', 'D'; #works fine! map2{ print $a $b; } map {$_} qw{A B C D}; #works map2{ print $a $b; } qw{A B C D}; #doesn't compile (as you said)

    I don't know if this is ActiveState specific or 5.6 specific - the only 5.6 I have is ActiveState

      Not a 5.6 (at least not a debian woody 5.6.1 problem) problem:

      ii perl 5.6.1-8.2 Larry Wall's Practical Extraction and Report Language.

      Works both ways.

      "Never be afraid to try something new. Remember, amateurs built the ark. Professionals built the Titanic."
Re: Problem emulating built-ins like map and grep.
by Anonymous Monk on Jan 21, 2003 at 20:49 UTC
      Wrong. You're talking about the \@ prototype, which makes Perl insist on seeing a @ sigil, and automatically takes a reference to that array before passing it to the function. You can still pass in lists by way of a construct like @{[foo_list()]}. It's exactly the same situation as with push et al.

      Makeshifts last the longest.

Re: Problem emulating built-ins like map and grep.
by Aristotle (Chancellor) on Jan 22, 2003 at 14:28 UTC
    It's a parsing issue. You'll find this works:
    map2 sub { print "$a $b" }, qw[A B C D];
    Update:
    $ perl -v
    
    This is perl, v5.6.1 built for i386-linux

    Makeshifts last the longest.

      With ActiveState Perl 5.6.1 build 633 on Win2K, this doesn't work. It compiles fine, but map2 just receives an empty list as the second argument.

Re: Problem emulating built-ins like map and grep.
by Aristotle (Chancellor) on Jan 22, 2003 at 19:06 UTC
    Btw, I seriously dislike your use of the binary operators for math there. You have much better options to gain speed without obfuscating the code. F.ex, if your @_ check never fails, your indiscriminate use will have cost you much more time than those two binary ops are ever going to save. Futhermore, you're mapping in void context - another waste of effort. (What was he thinking?? -- Ed.) Here is a version that I find more self documenting and more concise - and it even gets rid of one division entirely! :^)
    #!/usr/bin/perl -w use strict; sub map2 (&@) { my $code = shift; if(@_ % 2) { require Carp; Carp::croak('Odd number of values in list'); } local ($a, $b); my @r; push @r, $code->() while ($a, $b) = splice @_, 0, 2; @r; } my %hash = qw/A B C D E F G H/; # let's remove the multiple calls to print() and # make the use of a map justifiable, shall we? print map2 sub { "key $a => value $b\n" }, %hash;
    Update: I can't believe I didn't think about the list being returned. However, the while loop still avoids building a 1 .. @_ / 2 list, thus still saving effort.

    Makeshifts last the longest.

      In truth, the use Carp; (and the associated test for oddness) was tacked in the just for posting, it would always appear at the top of the program in real code. Does use have a huge runtime cost? I was under the impression that use was effectively a compile time directive?

      Personnally, coming from a C-background, I find ... if @_ & 1; an eminently readable and obvious test for 'oddness'. It wasn't done as an optimisation, it's just the clearest test for oddness that I know of. YMMV.

      The version of the code in my private library doesn't contain the oddness test at all. Though I think I will go back and add it, it already has a use Carp; atthe top anyway. I'll still use @_ & 1 though :)

      the while loop still avoids building a 1 .. @_ / 2 list, thus still saving effort.

      Aren't you just trading a >> and building one list, for building a local array and the then converting that back to list to return it? I doubt there's much in it in performance terms either way, but using an unnecessary intermediate variable seems very un-Aristotle-like:)

      re: # let's remove the multiple calls to print() and make the use of a map justifiable, shall we?

      As you pointed out yourself in your earlier post, your alternative wouldn't have demonstrated the problem I was describing, which was the only purpose of the code.


      Examine what is said, not who speaks.

      The 7th Rule of perl club is -- pearl clubs are easily damaged. Use a diamond club instead.

        Aren't you just trading a >> and building one list, for building a local array and the then converting that back to list to return it?
        You are building two lists, one as the dummy input to map, and one as its return value. The while loop makes the latter explicit and renders the former superfluous. You may have a point in that there's a hidden second list built to return the array contents (I'm not sure about that) - that just means the solutions are even though.
        I doubt there's much in it in performance terms either way, but using an unnecessary intermediate variable seems very un-Aristotle-like:)

        You're right. And I say that because I don't like either solution much. I prefer the while approach better here since it reads more naturally, as opposed to the dummy list "hack" you need for map. All that said and done though, it really is an ugly shortcoming of Perl that all of the list oriented operators can only step through a list one element at a time. And because that's true for all of them, homegrown solutions are inevitably ugly too.

        Finally, it occured to me that if you insist on map, you could generate the dummy list more economically since you don't actually use its elements:

        map { ... } (1)x(@_/2); # or even map { ... } (undef)x(@_/2);
        Esp the latter will probably take a lot less memory to process very large lists (though still a significant amount).

        Makeshifts last the longest.