Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

String replacement with .subst in a for loop

I'd like to make a string substitution in a for block using a named capture. I've expected to get the numbers 1,2,3 as output. But it is Nil for the first run, and then 1 and 2 for the 2nd and 3rd run. How do I use the .subst correctly in the loop construct? I see the same behavior when using a map construct instead the for loop. It does work as expected, if I replace with a fixed string value.

for <a1 b2 c3> -> $var {
    say $var;
    say $var.subst(/.$<nr>=(\d)/, $<nr>); #.subst(/.$<nr>=(\d)/, 'X'); #OK      
}

#`[
This is Rakudo version 2019.11 built on MoarVM version 2019.11   
Output:

a1
Use of Nil in string context
  in block  at test3.pl6 line 3

b2
1
c3
2
]
like image 233
LuVa Avatar asked Jan 22 '20 19:01

LuVa


People also ask

How do you replace a character in a for loop and a string?

Replace a character in a string using for loop in python We can replace a character in a string with another character in python by using the replace() function or sub() function or a for loop.


1 Answers

TL;DR Defer evaluation of $<nr> until after evaluation of the regex. @JoKing++ suggests one way. Another is to just wrap the replacement with braces ({$<nr>}).

What happens when your original code calls subst

Before Raku attempts to call the subst routine, it puts together a list of arguments to pass to it.

There are two values. The first is a regex. It does not run. The second value is $<nr>. It evaluates to Nil because, at the start of a program, the current match object variable is bound to something that claims its value is Nil and any attempt to access the value of a key within it -- $<nr> -- also returns Nil. So things have already gone wrong at this point, before subst ever runs.

Once Raku has assembled this list of arguments, it attempts to call subst. It succeeds, and subst runs.

To get the next match, subst runs the regex. This updates the current match object variable $/. But it's too late to make any difference to the substitution value that has already been passed to subst.

With match in hand, subst next looks at the substitution argument. It finds it's Nil and acts accordingly.

For the second call of subst, $<nr> has taken on the value from the first call of subst. And so on.

Two ways to defer evaluation of $<nr>

@JoKing suggests considering use of S///. This construct evaluates the regex (between the first pair of /s) first, then the replacement (between the last pair of /s). (The same principle applies if you use other valid S syntaxes like S[...] = ....)

If you use subst, then, as explained in the previous section, Raku puts together the argument list for it before calling it. It finds a regex (which it does not run) and a closure (which it does not run either). It then attempts to call subst with those arguments and succeeds in doing so.

Next, subst starts running. It has received code for both the match (a regex) and the substitution (a closure).

It runs the regex as the matching operation. If the regex returns a match then subst runs the closure and uses the value it returns as the substitution.

Thus, because we switched from passing $<nr> as a naked value, which meant it got frozen into Nil, to passing it wrapped in a closure, which deferred its evaluation until $/ had been set to a match with a populated <nr> entry, we solved the problem.

Note that this only works because whoever designed/implemented subst was smart/nice enough to allow both the match and substitution arguments to be forms of Code (a regex for the match and ordinary closure for the substitution) if a user wants that. It then runs the match first and only then runs the substitution closure if it's been passed one, using the result of that latter call as the final substitution. Similarly, S/// works because that has been designed to only evaluate the replacement after it's first evaluated the substitution.

like image 171
raiph Avatar answered Sep 20 '22 07:09

raiph