Often in my coding labors I wish for alternative forms of certain infix operators, such as + or &&, that I could use with an arbitrary number of operands. This is no mere boast of monkly laziness! It's not just a matter of not wanting to write all those +s and &&s. There are times when the number of operands is not known until runtime... Well, come to think of it, it is still a case of monkly laziness.

A very risky kluge I have encountered in my readings of Perl code is this sort of thing:

$sum = eval join ' + ', @addends;
or
$all_true = eval join ' && ', @flags;
Yikes. If @flags contains an undef the last expression "works" only because the eval fails with a syntax error. The counterpart using ' || ' can't even pretend to work.

A better alternative is to go ahead and define

sub AND { ! grep !$_, @_ } sub OR { !!grep $_, @_ }
Simple, nice and symmetrical, and they do the right thing even for an empty @_:
DB<1> p AND() 1 DB<2> p AND(0) DB<3> p AND(1) 1 DB<4> p AND(2, 3, 0, 1) DB<5> p OR() DB<6> p OR(1) 1 DB<7> p OR(2, 3, 0, 1) 1
The fly in this ointment is that, unlike && and ||, AND and OR as defined above are not short-circuiting: every member of @_ is checked by grep, whether the answer requires it or not. It would be nice to have something like grep that returned only the first element of a list that passed a test.

As it happens, there is such a thing in List::Util. From the docs:

first BLOCK LIST
Similar to "grep" in that it evaluates BLOCK setting $_ to each element of LIST in turn. "first" returns the first element where the result from BLOCK is a true value. If BLOCK never returns true or LIST was empty then "undef" is returned.
OK, so we try
use List::Util 'first'; sub AND { ! first { !$_ } @_ } sub OR { !!first { $_ } @_ }
This OR works fine:
DB<1> p OR(0) DB<2> p OR() DB<3> p OR(1) 1 DB<4> p OR(undef, 2, 0, 'x') 1
The AND, not so good:
DB<1> p AND(0) 1 DB<2> p AND() 1 DB<3> p AND(1) 1 DB<4> p AND(undef, 2, 0, 'x') 1
The problem is that in this implementation of AND, the outer ! is no longer negating the answer to the question "how many falses did you find", but rather it's negating that first false found. But it's worse than that: first is inherently ambiguous with tests like { ! $_ } or { ! defined $_ }, because a return value of undef could either mean "I didn't find any" or "I found this one." Maybe first is not the way to go.

OK, let's try something slightly more verbose but more straightforward:

sub AND { $_ || return 0 for @_; 1 } sub OR { $_ && return 1 for @_; 0 } __END__ DB<1> p AND(0) 0 DB<2> p AND() 1 DB<3> p AND(1) 1 DB<4> p AND(undef, 2, 0, 'x') 0 DB<5> p OR(0) 0 DB<6> p OR() 0 DB<7> p OR(1) 1 DB<8> p OR(undef, 2, 0, 'x') 1
It is good. The gods of Perl have found pleasure in putting a little bit of || in AND, and a little bit of && in OR. A case of Perl ying-yang.

But even though List::Util::first did not help with AND and OR, the very handy function List::Util::reduce can be used to generate multi-argument counterparts of other infix operators:

use List::Util 'reduce'; sub add { reduce { $a + $b } ( 0, @_ ) } sub mult { reduce { $a * $b } ( 1, @_ ) }
(The added 0 and 1 make sure that these behave righteously when @_ is empty.) List::Util::reduce is a truly handy little function, one that I wish had made to the Perl core; the docs have more examples of its usefulness.

And while I'm praying to the gods of Perl for new admissions to the core, let me pray for multiarg counterparts for all those infix operators for which they would make sense.

the lowliest monk

Replies are listed 'Best First'.
Re: An infix fix
by dragonchild (Archbishop) on Mar 22, 2005 at 14:11 UTC
    use Quantum::Superpositions qw( any all ); if ( all( undef, 2, 0, 'x' ) ) { print "They're all true!\n"; } else { print "At least one is false!\n"; } if ( any( undef, 2, 0, 'x' ) ) { print "At least one is truee!\n"; } else { print "They're all false!\n"; }

    And, they will be in Perl6.

    Being right, does not endow the right to be rude; politeness costs nothing.
    Being unknowing, is not the same as being stupid.
    Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
    Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

      And, they will be in Perl6.

      And they will have the much better name of Junctions. See Synopsis 9 and 3 for more info. They also currently work in Pugs as well:

      #!/usr/bin/pugs use v6; if ( undef() & 2 & 0 & 'x' ) { print "They're all true!\n"; } else { print "At least one is false!\n"; } if ( undef | 2 | 0 | 'x' ) { print "At least one is truee!\n"; } else { print "They're all false!\n"; }
      NOTE: The undef() in the first 'if' currently needs to be there or it confuses the parser, this is a Pugs-Bug though, so it will be fixed eventually.

      -stvn

        I see a difference between this and the any() / all() usage. How would you do

        if (all(@results)) { ... }

        using the

        if (x & y & z) { ... }

        style of coding it?

        /me is asking a question just in case I am missing something, since I don't see the relation between the two styles. One can process a list, the other cannot.

        --MidLifeXis

      Quantum superposition!? Heavens! Isn't that a bit like using a thermonuclear device to kill a fly? Maybe this another case of one of those regrettable module names. :-)

      I must learn more about it

      the lowliest monk

        It's more of a joke by the esteemed Damian Conway. Remember - most of Perl is a thermonuclear device ... which is why it's so good at swatting flies. :-)

        Being right, does not endow the right to be rude; politeness costs nothing.
        Being unknowing, is not the same as being stupid.
        Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
        Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

Re: An infix fix
by ihb (Deacon) on Mar 22, 2005 at 14:45 UTC
Re: An infix fix
by Jenda (Abbot) on Mar 22, 2005 at 14:24 UTC

    I don't think they deserve to be added to the core. They might be a good addition to List::Util, though. Actually the one you call add() already is there. It's called sum() ;-)

    use List::Util qw(reduce); sub reduce_c (&) { my $block = shift(); return sub {&reduce($block, @_ +)}} my $plus = reduce_c {$a+$b}; print $plus->(1,2,3,4,5), "\n"; *tajms = reduce_c {$a*$b}; print tajms(1,2,3,4,5), "\n";

    Jenda
    We'd like to help you learn to help yourself
    Look around you, all you see are sympathetic eyes
    Stroll around the grounds until you feel at home
       -- P. Simon in Mrs. Robinson

      Someone actually did extend List::Util with them: List::Util::Superpositions.

      Caution: Contents may have been coded under pressure.
Re: An infix fix
by gaal (Parson) on Mar 22, 2005 at 14:15 UTC
    You're in luck, because reduce looks like it's going to be in Perl6.

    Here's the draft Synopsis entry.

      And, interestingly enough, Python is losing its reduce. Everyone lynch Guido!

      "There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.

Re: An infix fix
by Roy Johnson (Monsignor) on Mar 22, 2005 at 16:22 UTC
    If you wanted to use List::Util::first for AND and OR, these work:
    sub AND { my $last; first {!($last = $_)} @_ or $last; } sub OR { my $last; first {($last = $_)} @_ and $last; }
    Again, the little bit of yin-yang.

    I note that the short-circuiting you mention isn't quite as good as the short-circuiting you get with the actual operators, since each element of the list will be evaluated prior to calling the function. So you don't have to traverse the entire list, but any function calls and assignments within the list will be executed.

    I note also that you're just exchanging the && operator for a comma (plus the function calling semantics). I had thought that overloading the comma operator would be a clever alternative, but it turns out not to be overloadable.

    Our functional-programming brethren might prefer the tail-recursive

    sub OR { @_ > 1 ? shift || OR(@_) : shift; } sub AND { @_ > 1 ? shift && AND(@_) : shift; }
    Update: tail-recursive ones were buggy.

    Caution: Contents may have been coded under pressure.
      Our functional-programming brethren might prefer the tail-recursive
      sub OR { @_ > 1 ? shift || OR(@_) : shift; } sub AND { @_ > 1 ? shift && AND(@_) : shift; }
      <pedantic> AND() should be true.
      sub OR { @_ ? shift || OR(@_) : 0; } sub AND { @_ ? shift && AND(@_) : 1; }
      </pedantic>

      the lowliest monk

        I really like the simplicity and symmetry, but I was (and still am) a little hung up on making them behave as their 2-argument operators do. So I certainly can't hold your pedantry against you. :-)

        If you call them ANY and ALL, I go with your definitions. As AND and OR, I think they should behave as the ops: AND returns the first false value, or the last true value if no false values are encountered; OR returns the first true value, or the last false value if no true values are encountered. That definition extends easily to one argument, but leaves the zero-argument case undefined.


        Caution: Contents may have been coded under pressure.

        Come to think of it, none of these recursive subs are tail recursive. They all have a pending && or || business when they return.

        I can't think of a simple way to make them tail recursive. The best I can come up with requires using "helper functions":

        sub OR { _OR (0, @_) } sub AND { _AND(1, @_) } sub _OR { my $s = shift; @_ && !$s ? _OR ( $s || shift, @_ ) : $s } sub _AND { my $s = shift; @_ && $s ? _AND( $s && shift, @_ ) : $s }

        the lowliest monk

Re: An infix fix
by hardburn (Abbot) on Mar 22, 2005 at 20:15 UTC

    I really wish more languages had taken the LISP route and defined all operators in prefix notation:

    (+ 1 2 (* 5 6 7) 9)

    I want more languages with either this or RPN. They both have their good points, but it's hard to do both.

    "There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.

      Lots of Irritating Silly Paranthesis? Hope not! (I like functional languages though, it's just the syntax of Lisp that makes me dizzy.)

      And actually is the result (3 30 7 9) or 222?

      RPN? You mean Reverse Polish Notation? Oh my. Man you do have some pretty perverted tastes ;-)

      Jenda
      We'd like to help you learn to help yourself
      Look around you, all you see are sympathetic eyes
      Stroll around the grounds until you feel at home
         -- P. Simon in Mrs. Robinson

        I don't know LISP and even I know the result is 222. The prefix operators are greedy.

        Being right, does not endow the right to be rude; politeness costs nothing.
        Being unknowing, is not the same as being stupid.
        Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
        Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

        It's all about efficiency, both of the computer and yourself. LISP has an extremely simple grammar and makes it easy to operate on large lists. RPN lets you do away with operator precedence tables without using parans.

        "There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.

      Yuck.

      (not readable (Lisp is) unless (like Yoda (you speak) ))

      What's more:

      if ( $perl{readiblity} > $lisp{readibility} ) { $lisp{readibilty} = "Bad"; }

      --
      Ytrew

        > (not readable (Lisp is) unless (like Yoda (you speak) ))
        
        I am already digging it.
        
        More prefix notations please!
        
Re: An infix fix
by parv (Parson) on Mar 27, 2005 at 01:25 UTC

    I would have thought of creating a function which went through the list before the eval() usage in OP. In any case, and operation could have been easily implenented something like ...

    # Adjust truth value tests & returned truth values as you like. sub short_circuit_list_and { my @list = @_; return unless scalar @list; my $and = 1; foreach my $i (@list) { return 0 unless $i; $and = $and && $i ? 1 : 0; } return $and; }

    Am i seriously missing something?

    UPDATE: I could not settle w/ the ternary operator inside the loop, so a variation ...

    # Adjust truth value tests & returns as you like. sub list_and { my @list = @_; return unless scalar @list; my $and = 1; foreach my $i (@list) { return 0 unless $i; $and &&= $i; } return $and ? 1 :0; }
      - Parv