2

So I answered a question on SO and got a lot of flack for it. I have been using Perl for many years and use this topicalising quite a lot.

So let's start with some code. I am doing search and replace in these examples. The idea is to search for one and three from two strings and replace them.

$values = 'one two three four five';
$value2 = 'one 2 three four 5';

$values =~ s/one//g;
$values =~ s/three//g;

$values2 =~ s/one//g;
$values2 =~ s/three//g;

This code is simple and everyone accepts it.

I can also build an array or hash with a list of values to search and replace which is also acceptable.

However, When I build a script to topicalise $values and $values2 and lessen the amount of typing to build a script it seems to be misunderstood?

Here is the code.

$values = 'one two three four five';
$value2 = 'one 2 three four 5';

for ( $values, $values2 ) {
    s/one//g;
    s/three//g;
}

The above code will topicalise the variables for the duration of the for block, but many programmers are against this. I want to understand why this is unacceptable?

Gerhard
  • 22,678
  • 7
  • 27
  • 43
  • Why don't you ask those "many programmers"? – melpomene May 20 '17 at 06:38
  • Well, that is what I am doing, it is right here that I got the flack, hence why I am asking it here. – Gerhard May 20 '17 at 06:42
  • It is not an arrogant question, I truly need to understand this as I cannot really find any reference anywhere that says it is bad coding except for the comments of "it is bad" – Gerhard May 20 '17 at 06:45
  • 1
    Well. For one thing, you are living on the edge a little bit, by changing things in place, but that of course isn't a problem in itself. Such code can very easily evolve to become a trap for the maintainer; that is a problem. The example you show doesn't save you that much and is less clear than other ways. However, I don't hesitate to do `s/$patt/$repl/ for @ary` when that is exactly what needs to be done. My .02 – zdim May 20 '17 at 06:47
  • 3
    I've found your answer and linked it. Two things: It was only one person who didn't like it, and the code in it was different from what you posted here. – melpomene May 20 '17 at 06:47
  • @zdim, thanks for the response. So as far as perl standards go, is it just plain bad coding because of the fact that it becomes difficult to maintain? – Gerhard May 20 '17 at 06:52
  • Topicalizing `$_` is done regularly, it's the fact that this changes the object over which you iterate in place that may evolve into tricky code. – zdim May 20 '17 at 06:52
  • @melpomene. Thanks. Yes one person that commented, but numerous people upvoted his comments and downvoted my answer, so more than one agrees with him. The values are different, but the code is the same. – Gerhard May 20 '17 at 06:53
  • I wouldn't call it "bad coding," and I don't know of Perl best practices on that. If the little loop evolves and the in-place changes become hidden or convoluted and ... it can get tricky and can definitely turn into horrible code. So it may be pushing it in a wrong direction. Again, purely my opinion. – zdim May 20 '17 at 06:54
  • ok thanks a mill @zdim. That is what I expected more or less :) – Gerhard May 20 '17 at 06:56
  • 2
    I agree that the main aspect is that it's confusing the maintenance guy. If I would come across this code in the code base at `$work`, I would refactor it. – simbabque May 20 '17 at 07:42
  • 1
    Thanks @simbabque. You guys helped me to confirm my thoughts :) – Gerhard May 20 '17 at 10:32
  • @zdim: *"you are living on the edge a little bit, by changing things in place"* That is how `$var =~ s///` works, unless you're advocating `(my $new = $var) =~ s///`? And it's nothing to do with the `for` topicaliser that seems to be the issue. – Borodin May 20 '17 at 20:46
  • 1
    @zdim: *"Such code can very easily evolve to become a trap for the maintainer"* so are you an opponent of using `$_` at all? Otherwise I don't see your p;oint? – Borodin May 20 '17 at 20:48
  • @simbabque: *"If I would come across this code in the code base at $work, I would refactor it"* We have a fist fight on our hands! – Borodin May 20 '17 at 20:50
  • @Borodin I think you also owe me some pints and you promised to drag me off the ferry. I'm looking forward to all of that! But seriously, if it has a couple of variables like here, it's probably fine. But if it's just one like the answer the OP linked then I would refactor. – simbabque May 20 '17 at 20:55
  • @simbabque: I have to wait for you to get *onto* the ferry first. The beers will come later, and the fight after that. – Borodin May 20 '17 at 21:05
  • @Borodin What I meant to say is that changing the array that is being iterated over (in place) can lead to tricky/bad code. (I added another comment in an attempt to clarify this.) I've seen too much code that changes what it iterates over which evolved from an OK design into a minefield. I use `$_` regularly (as you know) and didn't mean to say a thing against that in general. – zdim May 21 '17 at 01:18
  • @Borodin The only thing I'd change really is `s/one|three//g for (LIST)` – zdim May 21 '17 at 01:28

2 Answers2

6

There are several points to consider.


Your code performs multiple substitutions on a list of variables. You can do that without using $_:

for my $s ($values, $values2) {
    $s =~ s/one//g;
    $s =~ s/three//g;
}

Personally I think nothing is wrong with the above code.

The general problem with $_ is that it's not a local variable. E.g. if the body of your for loop calls a function (that calls a function ...) that modifies $_ without localizing it (e.g. by assigning to it or using a bare s/// or using while (<...>)), then it will overwrite the variables you're iterating over. With a my variable you're protected because other functions can't see your local variables.

That said, if the rest of your code doesn't have this bug (scribbling over $_ without localizing it), $_ will work fine here.


However, the code in your answer people originally complained about is different:

for ($brackets) {
    s/\\left/(/g;
    s/\\right/)/g;
}

Here you're not trying to perform the same substitutions on many variables; you're just saving yourself some typing by getting rid of the $brackets =~ part:

$brackets =~ s/\\left/(/g;
$brackets =~ s/\\right/)/g;

Using an explicit loop variable wouldn't be a solution because you'd still have to type out $foo =~ on every line.

This is more a matter of taste. You're only using for for its aliasing effect, not to loop over multiple values. Personally I'm still OK with this.

melpomene
  • 84,125
  • 8
  • 85
  • 148
  • 2
    The way `for` was used in the answer reminds me of languages like Visual Basic, where there is a [`with` block](https://learn.microsoft.com/en-us/dotnet/articles/visual-basic/language-reference/statements/with-end-with-statement) (this is .net, I meant VB5 but couldn't find it). [JavaScript also has that](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/with). A `for` loop in Perl can be used do the same thing, but as melpomele says, it comes with side effects that the implementations ino other languages that are meant for exactly this do not have. – simbabque May 20 '17 at 07:40
  • Thanks a mill guys! I have my answer. – Gerhard May 20 '17 at 10:31
  • *"Personally I think nothing is wrong with the above code"* and*"Personally I'm still OK with this"* Are you saying that you're uncomfortable with the OP's version that uses `$_` and believe that an explicit mentioning of a control variable or the object variable itself is required? – Borodin May 20 '17 at 13:57
  • 1
    The problem I have with `for ($text) { s/\\left/(/g; s/\\right/)/g; }` is the two uses of the regex engine! I'd use `my %brackets = ( left => "(", right => ")" ); $text =~ s/\\(left|right)/$brackets{$1}/g;` – ikegami May 20 '17 at 20:20
  • 1
    @GerhardBarnard: It seems that, after I had a word in your detractor's ear, and raised the thread with the moderators, your negative comments are gone and you have recouped one of your downvotes. Well done. – Borodin May 20 '17 at 20:44
  • awesome, Thanks @Borodin – Gerhard May 21 '17 at 10:45
  • @ikegami. I agree yes that using `%brackets` is a better method, I just needed to confirm if using `for ($brackets)` is really a big issue. Thanks for the response. – Gerhard May 21 '17 at 10:48
4

perldoc perlsyn has this

The foreach is the non-experimental way to set a topicalizer.

The OP's construct is a perfectly valid way of writing Perl code. The only provisons I have regarding their earlier answer are

  • Unlike the example here, only two operations were being applied to a single variable. That is only marginally briefer than simply writing two substitutions and I wouldn't bother here, although I may consider

    s/one//g, s/three//g for $value;
    
  • Other than the topicaliser, the answer is identical to another one already posted. I don't believe this makes it sufficiently different to warrant another post

Borodin
  • 126,100
  • 9
  • 70
  • 144