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

Inspired by having recently worked my way through Aristotle's nice article on the Y Combinator in Perl, I wondered if Howto avoid large hard-coded else-if function calls could be done using a similar technique. I eventually got it to go, but only by moving to 5.8.8 (rather than 5.8.6) and applying some hacks to work around (further) limitations of the List::Util::reduce() operator.

In the following code, the list of functions to be applied are built up (curried?) together to produce a single code ref that can be applied to the inputs and produce the outputs in a single pass (kinda):

#! perl -slw require 5.8.8; use strict; use List::Util qw[ reduce ]; $a = $b; my %dispatch = ( A => sub{ map{ $_ +1 } @_ }, B => sub{ map{ $_ *2 } @_ }, C => sub{ map{ $_**2 } @_ }, D => sub{ map{ $_ +3 } @_ }, E => sub{ map{ $_ *5 } @_ }, ); my $func_com = $ARGV[ 0 ] || 'ABC'; my @some_val = 1 .. 10; my $combo = reduce{ { ## 1 my( $x, $y ) = ( $a, $b ); ## 3 $a = sub{ $y->( $x->( @_ ) ) } } ## 2 } map{ $dispatch{ $_ }||sub{ @_ } } split '', $func_com; printf "[ %s ]", join ',', $combo->( @some_val ); __END__ c:\test>\AS817\perl\bin\perl5.8.8.exe junk6.pl ABCDE [ 95,195,335,515,735,995,1295,1635,2015,2435 ]

The hacks (this doesn't work before 5.8.8 because of closure bugs with reduce() that got recently fixed):

  1. (## 1 & ## 2) It doesn't work unless I nest a bare block inside the er, bare block passed to reduce().

    Shouldn't the outer bare block be sufficient to form the closures?

  2. (## 3) It also doesn't work unless I assign the globals $a & $b to lexicals.

    It's perfectly possible to close over package globals in other instances, so is this a bug?

    Or is it the alias nature of $a & $b that prevents the closure working correctly?

Both hacks are still required of 5.8.9 5.9.x (circa a month or so ago), so are either or both worth raising a perlbug over?


Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
"Too many [] have been sedated by an oppressive environment of political correctness and risk aversion."

Replies are listed 'Best First'.
Re: Bugs? Or only in my expectations?
by Corion (Patriarch) on Jun 24, 2007 at 10:10 UTC

    Regarding your point #3, I believe that, exactly because $a and $b are globals, closing over them doesn't make much sense. You'll have to copy the values out of the global variables (well, package lexicals), if you need to retain the values as found during the reduce call. As an aside, I've never used assignment to $a in reduce - I thought the assignment was implicit from the code return, as in:

    my $sum = reduce { $a + $b } 0,1,2; # instead of my $sum = reduce { $a = $a + $b } 0,1,2;

    As for you needing that second inner block, I don't know what happens there. On 5.8.5, I get a "deep recursion" in the call to $combo if I remove that second block. I guess it has something to do with pad allocation and maybe the known closure bug where you need to mention the variables to be closed over in the inner block in the intermediate block - we have a double-nesting of anonymous subs here too, the code block passed to reduce and the anonymous sub in there.

      1. #3, I believe that, exactly because $a and $b are globals, closing over them doesn't make much sense.

        Yes. I musta been getting punch drunk when I said "It's perfectly possible to close over package globals in other instances,".

        'Closing' over a global, just means referencing a global. You just get whatever value it currently has, if any. In this case none, because the reduce code localises it.

      2. As an aside, I've never used assignment to $a in reduce - I thought the assignment was implicit

        You might be surprised at the other things I tried before getting it to work in 5.8.8 :)

        The affect is the same either way. reduce{ $a + $b }... and reduce{ $a += $b }... both end up with $a = $a + $b after each iteration.

        The latter just makes it a little clearer to the casual observer maybe?

      3. that second inner block, .... On 5.8.5, I get a "deep recursion" in the call to $combo if I remove that second block.

        Recursion? There shouldn't be any recursion.

        But hey! If we could work out what is going on and formalise it, we'd have achieved the Y Combinator without all those messy duplicate anonymous subs and extra parameters :)


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.
        The affect is the same either way. reduce{ $a + $b }... and reduce{ $a += $b }... both end up with $a = $a + $b after each iteration.

        The latter just makes it a little clerer to the casual observer maybe?

        Urm... What?? reduce is not a function a "casual observer" can easily understand, without fully grasping what the function is for. Misusing it in a way like you do "to make clearer what it does" is actually getting away from the familiar path for the people who do know it, into unknown territory.

        Don't do that. Use reduce only in the way it was intended to be used. That'll be clearer for everybody, in the end.

Re: Bugs? Or only in my expectations?
by ikegami (Patriarch) on Jun 24, 2007 at 17:36 UTC

    The bare block is only needed when using the XS version of reduce, so it's an XS bug. The XS version uses something called MULTICALL for which I haven't found documentation. It could be some sort of optimization that fails in this scenario.

Re: Bugs? Or only in my expectations?
by ysth (Canon) on Jun 24, 2007 at 18:23 UTC
    By the way,
    require 5.8.8;
    will get you "v-string in use/require non-portable at junk6.pl line 2." in 5.10.