Front page | perl.perl6.language | Postings from February 2005

## Re: Fun with junctions (was Sets vs Junctions)

From:
Date:
February 11, 2005 15:48
Subject:
Re: Fun with junctions (was Sets vs Junctions)
Message ID:
```Patrick R. Michaud wrote:

>For example, with the "less than or equals" (<=) relational operator,
>the expression
>
>    any(2,3,4) <= 3
>
>becomes
>
>    any( 2 <= 3,    # 1  (true)
>         3 <= 3,    # 1  (true)
>         4 <= 3     # 0  (false)
>    )
>
>which ultimately becomes any(1,0), because <= is an operator that
>returns booleans.  In plain English, we're asking "Are any of the values
>2, 3, or 4 less than or equal to 3?", and the answer is "yes", because
>any(1,0) evaluates to true in a boolean context.  Note that it does
>*not* becomes the junction of the values that were less than or equal to 3
>-- for that we would use C<grep>.
>
>
I would argue that this sort of relational comparison is of limited
usefulness. Invariably, the next question that will nearly always be
asked is "_Which_ values worked / didn't work?". Using my set notation,
one gets the result set, and can then easily test for empty set to see
if there was anything meeting that condition. Not mentioned before, but
I had assumed that in boolean context, and empty set to be false, and
non-empty true.

so, where you say:

if any(1,2,3,4) <= 3 {...}

I would have a bit more complex:

if (1,2,3,4) & {\$^a <=3} {...}

however, I could also say:

if ((1,2,3,4) & {\$^a <=3}).elems > 2 {...}

To ask if "more than two elements are <= 3".
One could also then do relative simple things like:

for (1,2,3,4) & {\$^a <=3} -> \$a {   ...  }

Assuming that the {\$^a <= 3} is some sort of meaningful threshold, one
would likely have defined a virtual set for it, so that becomes:

for (1,2,3,4) * #threshold -> \$a {   ...  }

>Similarly, consider
>
>    all(2,3,4) <= 3
>
>
!((2,3,4) * #threshold)
or
#{2,3,4}.max <= 3

both of which are not quite as clean as your example.

I am willing to consider replacing the somewhat sloppy .min/.max for a
.any/.all to render it:

#{2,3,4}.all <=3

But I'm not sure I'm all that happy w/ the autothreading implications of
that. Especially if you are calling a function with side effects. What
if one value causes a 'die'? Does it throw an exception, even though
other values succeeded fine? And how utterly impossible is it going to
be to debug a program that gets an inadvertent junction/set thrown in
somewhere. Like:

\$x = \$Value | 'Default';
\$x = \$Value || 'Default';

If you have | return a set, not a junction, you will quickly get an
error down the road about using a set as a scalar, giving you somewhere
to look. As opposed to a potential constant weaving in and out of
autothreading, with junctions in half the places you were expecting scalars.

Consider:

\$a = package::func(); # returns a lovely ('cat'|'mouse')
say "Splat! \$a";

Do we get:

Splat! cat
Splat! mouse

Or just one of them, at apparent random?
Return a set, and the unsuspecting user gets:

Splat! SET(0xFFFFFFF)

Which, while not ideal, it will happen consistently, and be easier to
deal with.

threading, and then convert any() and all() to more orthogonal and() and
or(), we could do something like:

but even here, are we not better served with the hyper operators?

and((2,3,4) »<= 3)

btw, I like and()/or() over all()/any() because it makes it very clear
we are establishing a boolean context.

>But wait, there's more!  Junctions are valuable because we can combine
>them into multiple operands or function arguments.  Thus,
>
>    # intersection:  Are any values in @foo also in @bar?
>    any(@foo) == any(@bar)
>
>
#foo * #bar   # and we even know which ones!

>    # containment:  Are all of the elements in @foo also in @bar?
>    all(@foo) == any(@bar)
>
>
#foo <= #bar

>    # non-intersection:  Are all of the elements in @foo not in @bar?
>    all(@foo) == none(@bar)
>
>
!(#foo *#bar)

>Of course, the value of junctions is that they work pretty much
>with any operation on scalar arguments.  Thus, if we define an
>is_factor() function as:
>
>    # return true if \$x is a factor of \$y
>    sub is_factor (Scalar \$x, Scalar \$y) { \$y % \$x == 0 }
>
>then we automatically get:
>
>    # are any of @foo factors of \$bar?
>    if is_factor(any(@foo), \$bar) { ... }
>
>    # are all of @foo factors of \$bar?
>    if is_factor(all(@foo), \$bar) { ... }
>
>    # is \$foo a factor of any elements of @bar?
>    if is_factor(\$foo, any(@bar)) { ... }
>
>    # is \$foo a factor of all elements of @bar?
>    if is_factor(\$foo, all(@bar)) { ... }
>
>    # are any elements of @foo factors of any elements of @bar?
>    if is_factor(any(@foo), any(@bar)) { ... }
>
>    # are all elements of @foo factors of all elements of @bar?
>    if is_factor(all(@foo), all(@bar)) { ... }
>
>    # a (somewhat inefficient?) is_prime test for \$bar
>    if is_factor(none(2..sqrt(\$bar)), \$bar) { say "\$bar is prime"; }
>
>
But what happens when you try to escape the boolean context? I'll
reiterate my autothreading concerns above.
And in these, you still have to do something completely different to
determine what the factors are.

Sometimes a short loop is a good thing.

btw, in my set notation, you get:

@bar * {is_factor(\$^a, \$foo)}