5
\$\begingroup\$

I'd like to improve this operator code if I can. I don't know enough Raku to write idiomatically (or any other way for that matter), but suggestions along those lines would be good also.

use v6d;

sub circumfix:<α ω>( ( $a, $b, $c ) ) {
    if ( $a.WHAT ~~ (Int) ) {
        so ( $a >= $b & $a <= $c );
    }
    else {
        my @b;
        for $a { @b.push( ( $_ >= $b & $_ <= $c ).so ) };
        so (False) ∈ @b;
    }
};

if (α 5, 0, 10 ω) {
    "Pass".say;
}
else {
    "Fail".say;
}
if (α ( 5, 7, 11 ), 0, 10 ω) {
    "Pass".say;
}
else {
    "Fail".say;
}

This does a range check on a variable or variables, between/bookended by $b (lower) and $c (upper). I wonder about using some sort of signature check paired with multi as a cleaner approach?

\$\endgroup\$
3
\$\begingroup\$

Regarding this code:

my @b;
for $a { @b.push( ( $_ >= $b & $_ <= $c ).so ) };
so (False) ∈ @b;

I'm sure that Raku has some form of the any operator that would make this code a one-liner. Having a three-liner is definitely not idiomatic Raku. :)

Did you intentionally use the & operator instead of &&? I'd think that && is more efficient, but that may only be my knowledge from other programming languages. I don't have any experience with Raku. From what I read about junctions, there should be an easier way to express this.

Your code is hard to read since you use the meaningless variable names a, b and c. It would have been better to call two of them min and max. The third can then go by any other name.

I would have expected the operator to be α $min $x $max ω, but that's not what you chose. In that case, I would have probably named the operator in_range and made it an infix operator in the form $x in_range ($min .. $max), if that's possible at all.

\$\endgroup\$
5
  • \$\begingroup\$ Definitely food for thought! I looked at junction but foud the docs to opaque to understand. The variable name re-ordering hadn't occurred to me, but likewise a good idea. Not at all sure what you mean by the first comment? I'd still need to convert the value(s) to True and False and then check for first false effectively doing an evaluation on any of them… \$\endgroup\$
    – hsmyers
    Dec 10 '19 at 17:12
  • 1
    \$\begingroup\$ I think that $a >= $b && $b <= $c is more efficient because if the first part evaluates to false, the second part $b <= $c does not have to be evaluated at all. The & operator, on the other hand, evaluates both sides, always. At least that's what most of the C-based languages do. \$\endgroup\$ Dec 10 '19 at 19:16
  • \$\begingroup\$ Agreed and incorporated along with parameter order and naming as well. \$\endgroup\$
    – hsmyers
    Dec 11 '19 at 3:17
  • \$\begingroup\$ Tentative one-liner: for $i { return (False) unless ( $_ >= $min && $_ <= $max ). }; \$\endgroup\$
    – hsmyers
    Dec 11 '19 at 13:53
  • \$\begingroup\$ Shorter tentative one-liner: for $i { return unless ( $_ >= $min && $_ <= $max ).so }; \$\endgroup\$
    – hsmyers
    Dec 11 '19 at 14:23
4
\$\begingroup\$

There is quite a lot that you can do to make this idiomatic. I'll also go a bit overboard and show some ways to fully polish it (emphasis on way overboard for most projects).

You mention potentially wanting to do multi. That's certainly possible here:

multi sub circumfix:<α ω>( ( $a where Positional, $b, $c ) ) { ... };
multi sub circumfix:<α ω>( ( $a where Int,        $b, $c ) ) { ... };

But ultimately it's unnecessary. As the other answer points out, Raku has a junction types built in. They can be made by using the routines (or operators) all (&), any (|), one (^) and none — the routines are available as both subs and methods. In your case, we can take advantage of the fact that junctions can be made on a single item or a list, so that regardless whether $a is a list of Int or just a single Int, we can make it into a junction. That right there can reduce your code to:

sub circumfix:<α ω>( ( $a, $b, $c ) ) {
   so ( $a.any >= $b & $a.any <= $c );
};

Although we're going to remove it entirely, it should be noted that & is a junction. This is slightly less efficient than using && which just checks whether both sides are true and evaluates to a Bool. But we won't need to do that at all. Raku allows for chained operators, and the comparison operators are defined as such. This means that we can simplify things even further to:

sub circumfix:<α ω>( ( $a, $b, $c ) ) {
   so $b ≤ $a.any ≤ $c;
};

so has very loose operator precedence, so we don't need parentheses here. The final two considerations are purely stylistic, but help for code maintaince. The names $a, $b, and $c aren't descriptive. You'd definitely want to use something better. Also, it's fairly common, though not universal, to give extra emphasis to the fact that we're not mutating the values by using sigil-less variables. Personally, I also like to use whitespace to indicate the tighter grouping when using deconstruction, but YMMV. Lastly, semicolons aren't needed when they're the final expression of a block, or when a block terminates the line, so we can remove the semicolons.

sub circumfix:<α ω>( (\val,\min,\max) ) {
   so min ≤ val.any ≤ max
}

In this case, using min/max/val without sigils maybe is a bit riskier as they are also routines, but it seems to workout okay.

If we want to go above and beyond for safety's sake, we can check the values in the signature. I'm assuming based on your original that you wanted to limit things to Int values so let's make it explicit, and specify our return value:

sub circumfix:<α ω>( (\val where .all ~~ Int, \min where Int, \max where Int) --> Bool) {
   so min ≤ val.any ≤ max
}

At this point, the signature is getting a bit unruly, and so I like to spread it across multiple lines. Particularly if you have several deconstructed arguments, this lets identation also show structure a bit more clearly (but in this case, it's less necessary of course):

sub circumfix:<α ω> ( 
    (
       \val where .all ~~ Int, 
       \min where         Int, 
       \max where         Int
    )
    --> Bool
) {
   so min ≤ val.any ≤ max
}

And don't forget that Raku's POD syntax means we can also add in documentation that could help users or automatically create documentation:

#| Determine if a value or values are between the given values (inclusive)
sub circumfix:<α ω> ( 
    (
       \val where .all ~~ Int, #= The Int value or values to be tested
       \min where         Int, #= The lowest acceptable value
       \max where         Int  #= The highest acceptable value
    )
    --> Bool
) {
   so min ≤ val.any ≤ max
}

IDEs like Comma are already able to pull some of this information to make coding easier. Using it on signatures is really only supported for generating help feedback automatically on command line usage, but it's only a matter of time before such things will be integrated into IDEs.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.