9

I've an script with a conditional and a pile of checks using the same variable. Something similar to this:

my $size = "123B";
say "OK" if $size ~~ Str && $size.ends-with("B") && $size.chop >= 0;

Would it be possible to rewrite the line in a way that would avoid the repetition of $size?. I thought something like junctions which I have no idea how to apply to that. Or maybe something else, but shorter to my line.

Brass P.
  • 103
  • 5

3 Answers3

11

How about:

my $size1 = "123B";
given $size1 {
      say "OK" if .Str && $_.ends-with("B") && $_.chop >= 0;
};

$_ is the 'Topic Variable' in Raku (and Perl as well). You can abbreviate to $_.ends-with(), or shorten the code even further to .ends-with(), as shown below. Similarly $_.chop can be shortened further to .chop, as shown below.


Or using the [&&] reduction meta-operator:

my $size2 = "123B";
given $size2 {
      say "OK" if  [&&] .Str, .ends-with("B"), .chop >= 0;
};

Or with an all() junction:

my $size3 = "123B";
given $size3 {
      say "OK" if  $_ ~~ all(.Str, .ends-with("B"), .chop >= 0);
}

Or eliminate the given block entirely:

my $size4 = "123B";
say "OK" if  $size4 ~~ all(.Str, .ends-with("B"), .chop >= 0);

https://course.raku.org/essentials/loops/topic/

jubilatious1
  • 1,999
  • 10
  • 18
  • I know, but in that case you are just changing the variable. I'm trying to avoid repeating of the same variable in each condition. Thanks anyway. – Brass P. Aug 26 '23 at 21:14
  • 2
    @BrassP. The `$_` can be omitted in the condition parts. Just write `.ends-with("B")` and `.chop >= 0`, for instance. That's the magic of the `$_` variable. – Silvio Mayolo Aug 26 '23 at 23:53
  • 1
    @BrassP. updated! – jubilatious1 Aug 27 '23 at 01:25
  • 1
    Thanks!. I love the metaoperator idea: `say "OK" if $size ~~ [&&] .Str, .ends-with("B"), .chop >= 0` – Brass P. Aug 27 '23 at 23:04
  • 1
    @jubilatious1 I like your last suggestion best -- but only if it's fixed. :) It doesn't gracefully handle `$size` not working as a string, doesn't complain if it's just `'B'` (it says that that's `OK`, which feels wrong), it will throw an exception (`Cannot convert string to number`) with, eg, `'AB'`, and may display warnings (eg if `$size` is declared as `my Str $size;` but not initialized). This code fixes all those things, and drops the `.Str` because my `try` tweak of your suggestion means it doesn't matter: `say "OK" if quietly try $size4 ~~ all .chars > 1, .chop >= 0, .ends-with("B")`. – raiph Aug 29 '23 at 01:05
3

{ when /B$/ { say 'OK' } } given my Str $size = '123B'

my answer is a bit cheeky since you know that $size is a Str if it matches the regex and you know if it has a B as the last char, then .chop >= 0

my $size = '123B';
{ when (Str && /B$/ && .chop >= 0) { say 'OK' } } given $size

this generalization is probably more in the spirit of what you asked

^^^ my first answer vvv corrections per comments

Here is my improved answer.

my $size = '123B'
{ when ( Str, /B$/, .chop >= 0 ).all { say 'OK' } } given $size;

Also my initial pass misunderstood the idea that the 123 of 123B was to be interpreted as a number ... I thought the idea of the .chop >= 0 part was to check the number of bytes in the string.

The biggest advance was to define a test ;-) like this:

my @test = '123B', '-12B', '123A', '123AB', 1234;
{ when ( Str, /B$/, .chop >= 0 ).all { say 'OK' } } for @test;

The individual tweaks were:

  • reinsert .chop >= 0
  • use an All Junction to distribute the when tests and not && ... thus avoiding the when ( TypeObject && .. ) trap

Thanks to the commenters I have learned a few things today!

librasteve
  • 6,832
  • 8
  • 30
  • 1
    Technically, this fails for negative values of `$size` (I know...negative Bytes are unlikely, but anyway). I can get the second example to correctly reject negative `$size` by changing `Str` to `.Str`. Not sure about how to correct the first example (guessing because the regex cannot detect negative numbers). – jubilatious1 Aug 27 '23 at 19:41
  • 1
    The second code solution won't work. Try changing the `$size` declaration to any string, eg the null string or `'aaa'`. It'll keep displaying `OK`. The problem is `TypeObject && ...`. A type object is `False` so it shortcircuits the `&&` expression, yielding the type object. Next, `when TypeObject { say 'OK' }` displays `OK` if `TypeObject` smartmatches the current topic. And that's true if the type object is `Str` and the given topic is a string -- any string. See [an answer of mine to an earlier SO](https://stackoverflow.com/a/49835992/1077672) for some discussion of this. – raiph Aug 28 '23 at 01:34
  • @raiph I'm wondering if the second code example is cured by `given $size { when ( .chars.so && /B$/ && .chop >= 0) { say 'OK' } };`. This properly handles negative values as well as empty strings. What it doesn't handle gracefully is a $-sigiled variable that's not a string but a Blob (an error will be thrown). But maybe the OP's okay with that? – jubilatious1 Aug 29 '23 at 00:01
  • "and you know if it has a B as the last char, then .chop >= 0." That ain't cheeky it's cheating! The code `.chop >= 0` isn't counting the number of characters but instead coerces the chopped string to a number and compares that with zero. So it'll fail (actually throw an error) for, say, `'AB'`, or `'123AB'`, or `'A123B'`. Granted, the original code skips many checks too, but it does include `.chop >= 0` and the challenge was to do *better*! I've added a comment to @jubilatious1's answer addressing the various weaknesses I've seen in all suggestions so far. – raiph Aug 29 '23 at 01:18
  • @jubilatious1 Your suggestion has other problems too. So do the solutions in your answer. I'll suggest something in a comment on your answer which fixes all the ones I see. Also, I'd just not bother with `when`. I don't see upsides to using it for this problem and I do see downsides (even if the error I pointed out was fixed). (That said, I immediately started trying solutions using `when` as soon as I read the Q. The `$size ~~ Str` code instantly led my mind in that direction. But then I remembered the `Str &&` problem and eventually realized `when` just didn't buy me what I was looking for.) – raiph Aug 29 '23 at 01:21
  • @raiph Either `Str && 0` or `Str && 1` are problematic because they're not acting on `$_`. It needs to be written `.Str && 0` or `.Str && 1`. – jubilatious1 Aug 29 '23 at 18:14
  • @jubilatious1 They're worse than problematic *regardless of what they're acting on*. First, any type object boolean coerces to `False`. So `TypeObject && ...`, no matter what the `...` is, will always return `False`. Second, this is so even though they *are* always acting on `$_` in a smart match, such as a `when`, which was the context for my comment. And in addition to realizing `when` was the wrong tool (imo), I also realized that if you add a `try` to protect against a range of possible problems, such as input like `'123.B'`, then the `.Str` is *redundant*. cf my comment on your answer. – raiph Aug 29 '23 at 23:21
  • @raiph we're going to have to agree to disagree then. In the REPL `'cat'.Str.so` returns `True`, and `1.Str.so` returns `True`, and `0.Str.so` returns `True`, but empty-string `''.Str.so` returns `False`. – jubilatious1 Aug 30 '23 at 03:16
  • @jubiliatious1 We don't *have* to disagree. :) Of course `.Str` works as you showed (if `$_` is set). My point was that characterizing `Str && 0` or `Str && 1` as "problematic" dramatically understates the problem. They are *always utterly useless*. So I disagreed with *the reason you gave for them being "problematic"* or, as I just put it, *utterly useless*. It's nothing to do with `$_`. In the code `when Str && 0 { ...}` or `when Str && 1 { ...}`, the `Str && ...` bit is *still* utterly useless, and *always would be*, and this is so even though they *are* being smartmatched against `$_`. – raiph Aug 30 '23 at 12:10
  • @librasteve _"...I thought the idea of the `.chop >= 0` part was to check the number of bytes in the string."_ I still believe that to be true, and I hope you do as well! – jubilatious1 Sep 01 '23 at 12:21
  • nope - ```'128B'.chop``` will give you ```Str|128``` then ```>=``` is a numeric compatison so that will coerce it to ```Int|128``` so then you you can have eg. ```'-128B'.chop >= -129; #True``` – librasteve Sep 01 '23 at 22:35
0

I've fallen into the habit of creating a subset when dealing with some things like this. Mainly because I find it nice to be able to re-use it in further ~~'s and to be able provide descriptive names for potentially complex logic:

Here's what I would do:

subset MyDescriptiveSubset where { all: .Str, .starts-with("f"), .chop >= 0 }

say "OK" if $size ~~ MyDescriptiveSubset
Rawley Fowler
  • 1,366
  • 7
  • 15