in reply to Re^4: Quote and Quote-like Operators
in thread Quote and Quote-like Operators

I'm not saying that heredocs aren't useful. Just that they break indentation.
Then you’re just doing them wrong. :)
sub compile_filter() { my @criteria; for my $i ( 0 .. $#ARGV ) { my $snippet = $ARGV[$i]; $snippet =~ s/^\s+//; # prime the autoloader on allcappish barewords if (my @capwords = $snippet =~ /\b (?=[A-Z]) ([A-Z0-9]+) \b/xg +) { eval deQQ<<"EO_AUTOLOADED_SUBS"; |QQ| |QQ| use subs qw(@capwords); |QQ| EO_AUTOLOADED_SUBS } # args starting with a backslash or which are a bracketed # espression are interpreted as pattern matches if ($snippet =~ m{ ^ \\ | ^ \[ .* \] $ }x) { $snippet = "/$snippet/"; } my $test_compile = deQ <<'START_TEST'; |Q| use warnings qw[FATAL all]; |Q| my $ignore = START_TEST $test_compile .= deQQ(<<"END_TEST"); |QQ| sub { $snippet }; |QQ| |QQ| # so eval returns true |QQ| 1; |QQ| END_TEST # debug("test compile:\n$test_compile"); eval($test_compile) || die "$0: invalid criterion in '$snippet': $@\n"; $criteria[$i] = "do { $snippet }"; } my $real_code = deQ(<<'START_CODE') . "\t"; |Q| use warnings; |Q| #use warnings qw[FATAL all]; |Q| #no warnings qw[deprecated]; |Q| |Q| sub filter { |Q| |Q| debug(sprintf("testing code point %X", ord())) +; |Q| |Q| my $result = |Q| START_CODE $real_code .= join("\n &&\n\t" => @criteria) . deQ(<<'END_CODE'); |Q| |Q| ; |Q| |Q| debug("result of " . join(" && ",@criteria) . +" is $result"); |Q| return $result; |Q| } |Q| |Q| # so eval returns true |Q| 1; END_CODE debug("CRITERIA are\n$real_code"); eval($real_code) || die; }
As you see, heredocs certainly don’t have to “break indentation”; it just takes a bit of creative processing to make ’em look purdy. The deQ and deQQ functions are trivial:
sub dequeue($$) { my($leader, $body) = @_; $body =~ s/^\s*\Q$leader\E ?//gm; return $body; } sub deQ($) { my $text = $_[0]; return dequeue q<|Q|>, $text; } sub deQQ($) { my $text = $_[0]; return dequeue qq<|QQ|>, $text; }
See how that all works?

--tom

Replies are listed 'Best First'.
Re^6: Quote and Quote-like Operators (heredocs break)
by tye (Sage) on Dec 16, 2011 at 16:26 UTC

    Even with all of that clever work, go ahead a 'shift' any of those blocks of code over and watch your heredoc break. Your heredocs above break indentation (your terminators aren't indented). And (re)indentation breaks your heredocs.

    Perhaps worse, trailing whitespace can break a Perl heredoc. Plenty of reason to avoid using them in code I plan to maintain.

    - tye        

      It's even worse than that. I recently discovered that an incorrect closing quote in a string will break it. So I insist on not using strings in code I plan to maintain.

      <but class="seriously">I don't disagree that acrobatics to make heredocs appear as normal code flow edges toward egregious.</but>

        I recently discovered that an incorrect closing quote in a string will break it.

        Cute.

        Things where "incorrect" can break it: everything. Things where "trailing whitespace" can break it: almost nothing, and for good reason. On the extremely rare occasions when I find something where trailing whitespace matters, it is usually just admitted as a bug. I can't even think of any cases of that right now except for heredocs.

        I can't even think of anything where

        one space

        makes a difference compared to

        two spaces

        And you can actually see that difference.

        And, yes, even ignoring all of that, making it easy to get the delimiter wrong for a heredoc is much worse than getting the delimiter wrong for a string.

        sub flutz { # ... my $blob= <<END; ... END # ... } sub flortz { # ... my $blob= <<END; ... END } if( int rand 2 ) { flortz(); }

        compiles completely fine. Of course, it often produces:

        Undefined subroutine &main::flortz called at - line 16.

        when you run it since I wrote the example to make it relatively trivial to notice that the intervening code has just "gone away".

        But I can't even make this example demonstrate why because the important part is invisible. (Update: Even highlighting the above code doesn't show the trailing whitespace, at least for my environment.)

        Construct an example where getting the closing quote wrong compiles without error. It won't be nearly as easy as this.

        And while you are trying, you'll probably have a chance to notice that Perl often goes out of its way to help you see your mistake via something like:

        blah blah blah at - line 13, near "blah" (Might be a runaway multi-line '' string starting on line 4)

        This is done because experienced programmers figured out that when getting the closing delimiter wrong it can be hugely frustrating to find the source of the problem.

        I'm not sure whether you were dismissive just to be funny or if you actually were trying to be dismissive. But I responded because I didn't want other readers to dismiss the point based on your clever dismissal, preventing them from thinking much about the point and realizing how crazy significant trailing whitespace is.

        - tye        

      Many of the developers @my_work use the 'clever':
      .... my $here_doc = '....END'; stuff here ....END

      Along with clever trim leading whitespace code...which I despise...so I use heredocs, but ONLY on the left margin. I've become fond of syntax highlighting, so any incorrect trailing marker is (usually) obvious. I've also taken to using some vim heredoc highlighting tips and always use the terminators SQL, XML, and HTML when appropriate.

      If I have a heredoc in a sub, I move it out of the sub, e.g.

      my $sql = <<SQL; SELECT * FROM TABLE SQL sub do_stuff { ... $dbh->execute($sql); }
      If the $sql needs to be dynamic, I'll probably change it to a sprintf template, and call sprintf in the sub.

        I much prefer join over sprintf (as well as over here-docs or multi-line quoted strings or concatenation). That preference took a lot of years to form but has now lasted almost as many years. Other techniques don't scale nearly so well, IME, in the face of on-going maintenance and growing size and complexity of the string and programmers' potential for simple mistakes.

        In the rare case of using a Perl here-doc as a quick solution, I also move it to be top-level. I also have a somewhat unusual editor configuration that includes showing trailing whitespace (something a significant percentage of my coworkers don't appear to have -- based on them seeming oblivious to the trailing whitespace that they sometimes commit). And even those here-docs rarely last very long.

        Heck, even in the short term I'd likely just replace it with a multi-line quoted string:

        ( my $string= q{ ... } ) =~ s/(?=^|(\n))\n[^\S\n]+/defined $1 ? "\n" : ''/ge;

        (Clever undenting regex included just to show how easy it is to eliminate the opening newline that this adds -- not because I find myself actually using such.)

        Having here-docs at the top level is also annoying. Not as annoying as not being able to re-indent code w/o breaking it, of course.

        SQL seems to be one of the most common examples of the use of a here-doc. I particular dislike using here-docs for SQL. By using join, I don't have to suffer due to SQL not allowing trailing commas, here-docs not supporting comments nor excluding lines conditionally, or lots of other things.

        But most SQL I write ends up looking more like:

        $db->select( [ "this", "that", "theother", "COUNT(*) as things" ], join( ' ', "foo", "LEFT JOIN bar USING( id )", ), [ "? <= stamp", "stamp < ?", @exclude ? "this NOT IN ( ?? )" : (), ], "GROUP BY this, that, theother", "ORDER BY that, this", "LIMIT $size OFFSET $skip", "HAVING 1 < count(*)", );

        Which just boils down to this slightly longer code that I write when I don't have such trivial wrappers handy:

        $db->prepare( join ' ', "SELECT", join( ', ', "this", "that", "theother", "COUNT(*) as things", ), "FROM", join( ' ', "foo", "LEFT JOIN bar USING( id )", ), "WHERE", join( ' AND ', "? <= stamp", "stamp < ?", @exclude ? "this NOT IN ( ?? )" : (), ), "GROUP BY this, that, theother", "ORDER BY that, this", "LIMIT $size OFFSET $skip", "HAVING 1 < count(*)", );

        (Except that I like to build the SQL first rather than pass it directly to prepare(), so that I can easily get literal SQL in case I need to play with it interactively in a simple SQL tool. This includes replacing the placeholders with quote()d values.)

        I find that scales much better as maintenance takes its toll.

        I've also repeatedly suffered due to multi-line SQL dripping with whitespace, especially leading whitespace, tripping up perhaps somewhat naive DB tools I've needed to use to diagnose DB problems.

        So I still feel quite justified in discouraging others from just running off and using here-docs, especially when I'm not planning on going on for thousands of words on how to avoid the many pitfalls.

        - tye        

      I totally agree with you. Heredocs, as implemented in Perl5 are a maintenance problem waiting to happen. I frankly cant stand it when I indent a chunk of code and breaks in all kinds of bizarro ways because it has stupid here docs in it.

      ---
      $world=~s/war/peace/g