tags:

views:

358

answers:

4
open $FP, '>', $outfile or die $outfile." Cannot open file for writing\n";
  • I have this statement a lot of times in my code.

  • I want to keep the format same for all of those statements, so that when something is changed, it is only changed at one place.

  • In Perl, how should I go about resolving this situation?

  • Should I use macros or functions?

I have seen this SO thread How can I use macros in Perl?, but it doesn't say much about how to write a general macro like

#define fw(FP, outfile) open $FP, '>', \
        $outfile or die $outfile." Cannot open file for writing\n";
+11  A: 

Perl 5 really doesn't have macros (there are source filters, but they are dangerous and ugly, so ugly even I won't link you to the documentation). A function may be the right choice, but you will find that it makes it harder for new people to read your code. A better option may be to use the autodie pragma (it is core as of Perl 5.10.1) and just cut out the or die part.

Another option, if you use Vim, is to use snipMate. You just type fw<tab>FP<tab>outfile<tab> and it produces

open my $FP, '>', $outfile
    or die "Couldn't open $outfile for writing: $!\n";

The snipMate text is

snippet fw
    open my $${1:filehandle}, ">", $${2:filename variable}
        or die "Couldn't open $$2 for writing: $!\n";

    ${3}

I believe other editors have similar capabilities, but I am a Vim user.

Chas. Owens
+23  A: 

First, you should write that as:

open my $FP, '>', $outfile or die "Could not open '$outfile' for writing:$!";

including the reason why open failed.

If you want to encapsulate that, you can write:

use Carp;

sub openex {
    my ($mode, $filename) = @_; 
    open my $h, $mode, $filename
        or croak "Could not open '$filename': $!";
    return $h;
}

# later

my $FP = openex('>', $outfile);

Starting with Perl 5.10.1, autodie is in the core and I will second Chas. Owens' recommendation to use it.

Sinan Ünür
I would say "could not" rather than "cannot" because who knows what the current state of that file is; we only know what the state was when we tried to open it (hence the past tense). Of course, that is a meaningless nicety. You could just as easily say "Bang for $filename: $!".
Chas. Owens
Oh, and `corelist` says Perl 5.10.1 was the first version with `autodie` in the core and the [delta](http://perldoc.perl.org/perl5101delta.html#New-Modules-and-Pragmata) backs that up.
Chas. Owens
@Chas - edited. @Sinan - +1 for "$!". Readable and actionable error messages are an often underappreciated art in software development
DVK
Sinan: This clearly works. I tested it. But isn't the behavior SUPPOSED to be that files opened with `my $h` are auto closed when $h goes out of scope? See http://perldoc.perl.org/perlopentut.html#Indirect-Filehandles and http://perldoc.perl.org/perlsub.html#Private-Variables-via-my().
drewk
@drewk The file will be closed when `$FP` goes out of scope or it is set to `undef`. Keep in mind that Perl is not C. In any case, it is a good idea to explicitly close filehandles opened for writing and check if there was an error.
Sinan Ünür
@Sinan: My comment is mainly that the documentation in perlopentut is wrong or, at least, misleading. At the end of `openex` `my $h` is lexically out of scope. The example in `indirect files handles` in perlopentut is very specific that `my $in` will not require closing and this is almost the same as your example. The document is, I believe, is in error. I wasn't saying that relying on autoclose is good practice or that a close should not be done; just attempting to describe what is or is not.
drewk
@drewk Are you comfortable with `my $y = f(); sub f { my $x = 'test' }`? "Unlike local variables in C or C++, Perl's lexical variables don't necessarily get recycled just because their scope has exited. If something more permanent is still aware of the lexical, it will stick around. So long as something else references a lexical, that lexical won't be freed--which is as it should be. "
Sinan Ünür
@Sinan: Agreed. I agree that `my` is different than auto variable in C; Perl vars sticks around if there are other references to it. The issue I have terminology. In your example of `my $y = f(); sub f { my $x = 'test' }` are you saying that `$x` is still "in scope" after the return from f()? The data is there and there is no risk it will go away like C. But is `$x` technically "in scope" outside f()? No. $x is lexical to f() alone. $y has the data (either by reference or by copy depending on the data, no?) but the only "in scope" way to access the data is through $y after return from f().
drewk
@drewk ... and `$h` is no longer in scope but because of the `return $h` and the fact that we assigned that return value to `$FP`, there is still a reference to the file handle. It will not be disposed of just like the string `'test'` will not be disposed of.
Sinan Ünür
@Sinan: Completely agreed. I thank you for your patience... The data (either from `$h` or `$FP`) is deleted only when its reference count=0. I think the only disagreement we may have is if the documentation in perlopentut is correct or incomplete as is. It states that `an indirect filehandle automatically closes when it goes out of scope`. The example has both `my $in` going out of scope and the filehandle reference count going to 0. The confusion I had is which event causes the autoclose. Its the later. Your `openex` is a better example than `firstline` in perlopentut for this reason. :-)
drewk
@drewk Ah-ah! I finally see it. I think I will submit a doc patch to clarify. At the very least, it should say, "the file will be closed when there are no more references to the handle" or something like that.
Sinan Ünür
@Sinan: YES!!! The start of that section is good: "open's first argument can be a reference to a filehandle. As of perl 5.6.0, if the argument is uninitialized, Perl will automatically create a filehandle and put a reference to it in the first argument" The later part, regarding the close when "it" goes out of scope is unclear. What is "it"? As stated, I think your `openex` here is a far better example for that section. Please submit that! I have been writing Perl for 10 years and did not realize you could pass anything other than a ref to a file glob in this case. (old man, I know...)
drewk
@drewk FYI: The patch was just applied: http://perl5.git.perl.org/perl.git/commit/d7d7fefda1ec0b9784e8148011dbac52e088dd91
Sinan Ünür
@Sinan Ünür: Thanks! I reviewed your addition and it is really much better.
drewk
+4  A: 

There are several ways to handle something similar to a C macro in Perl: a source filter, a subroutine, Template::Toolkit, or use features in your text editor.

Source Filters

If you gotta have a C / CPP style preprocessor macro, it is possible to write one in Perl (or, actually, any language) using a precompile source filter. You can write fairly simple to complex Perl classes that operate on the text of your source code and perform transformations on it before the code goes to the Perl compiler. You can even run your Perl code directly through a CPP preprocessor to get the exact type of macro expansions you get in C / CPP using Filter::CPP.

Damian Conway's Filter::Simple is part of the Perl core distribution. With Filter::Simple, you could easily write a simple module to perform the macro you are describing. An example:

package myopinion;
# save in your Perl's @INC path as "myopinion.pm"...
use Filter::Simple;
FILTER { 
    s/Hogs/Pigs/g; 
    s/Hawgs/Hogs/g;
    }
1; 

Then a Perl file:

use myopinion;
print join(' ',"Hogs", 'Hogs', qq/Hawgs/, q/Hogs/, "\n");
print "In my opinion, Hogs are Hogs\n\n";

Output:

Pigs Pigs Hogs Pigs 
In my opinion, Pigs are Pigs

If you rewrote the FILTER in to make the substitution for your desired macro, Filter::Simple should work fine. Filter::Simple can be restricted to parts of your code to make substations, such as the executable part but not the POD part; only in strings; only in code.

Source filters are not widely used in in my experience. I have mostly seen them with lame attempts to encrypt Perl source code or humorous Perl obfuscators. In other words, I know it can be done this way but I personally don't know enough about them to recommend them or say not to use them.

Subroutines

Sinan Ünür openex subroutine is a good way to accomplish this. I will only add that a common older idiom that you will see involves passing a reference to a typeglob like this:

sub opensesame {
   my $fn=shift;
   local *FH;
   return open(FH,$fn) ? *FH : undef;
}

$fh=opensesame('> /tmp/file');

Read perldata for why it is this way...

Template Toolkit

Template::Toolkit can be used to process Perl source code. For example, you could write a template along the lines of:

[% fw(fp, outfile) %] 

running that through Template::Toolkit can result in expansion and substitution to:

open my $FP, '>', $outfile or die "$outfile could not be opened for writing:$!"; 

Template::Toolkit is most often used to separate the messy HTML and other presentation code from the application code in web apps. Template::Toolkit is very actively developed and well documented. If your only use is a macro of the type you are suggesting, it may be overkill.

Text Editors

Chas. Owens has a method using Vim. I use BBEdit and could easily write a Text Factory to replace the skeleton of a open with the precise and evolving open that I want to use. Alternately, you can place a completion template in your "Resources" directory in the "Perl" folder. These completion skeletons are used when you press the series of keys you define. Almost any serious editor will have similar functionality.

With BBEdit, you can even use Perl code in your text replacement logic. I use Perl::Critic this way. You could use Template::Toolkit inside BBEdit to process the macros with some intelligence. It can be set up so the source code is not changed by the template until you output a version to test or compile; the editor is essentially acting as a preprocessor.

Two potential issues with using a text editor. First is it is a one way / one time transform. If you want to change what your "macro" does, you can't do it, since the previous text of you "macro" was already used. You have to manually change them. Second potential issue is that if you use a template form, you can't send the macro version of the source code to someone else because the preprocessing that is being done inside the editor.

Don't Do This!

If you type perl -h to get valid command switches, one option you may see is:

-P                run program through C preprocessor before compilation

Tempting! Yes, you can run your Perl code through the C preprocessor and expand C style macros and have #defines. Put down that gun; walk away; don't do it. There are many platform incompatibilities and language incompatibilities.

You get issues like this:

#!/usr/bin/perl -P
#define BIG small
print "BIG\n";
print qq(BIG\n);

Prints:

BIG
small

In Perl 5.12 the -P switch has been removed...

Conclusion

The most flexible solution here is just write a subroutine. All your code is visible in the subroutine, easily changed, and a shorter call. No real downside other than the readability of your code potentially.

Template::Toolkit is widely used. You can write complex replacements that act like macros or even more complex than C macros. If your need for macros is worth the learning curve, use Template::Toolkit.

For very simple cases, use the one way transforms in an editor.

If you really want C style macros, you can use Filter::CPP. This may have the same incompatibilities as the perl -P switch. I cannot recommend this; just learn the Perl way.

If you want to run Perl one liners and Perl regexs against your code before it compiles, use Filter::Simple.

And don't use the -P switch. You can't on newer versions of Perl anyway.

drewk
A: 
Choy