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';
instead of :
$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.


Hmm. If we instead declare autothreading dead, but instead have explicit threading, and then convert any() and all() to more orthogonal and() and or(), we could do something like:


and(#{2,3,4}.thread <= 3)

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)}


-- Rod Adams

Reply via email to