Crib Sheet Archives

Scoping out an even conciser fork idiom

Years ago I wrote about a concise fork idiom. It turns out that it’s possible to do better than everything I discussed in that entry as well as the proposals in the comments.

I didn’t at the time appreciate a clever aspect of variable scoping in Perl:

use strict;
sub get_answer { 0 }
if ( my $answer = get_answer ) {
    ...;
} else {
    print $answer;
}

This compiles and works as intended. In other words, a variable declared within an if or elsif conditional is in scope not just in that branch, but in all following branches (and elsif conditionals) as well. Which means it is possible to write the following:

if ( my $kid = fork ) {
    print "parent of $kid\n";
} elsif ( defined $kid ) {
    print "child of ", getppid, "\n";
} else {
    die "Couldn't fork: $!\n";
}

It doesn’t get more low-key than this.

I am sure there are many similar applications of the same idea in other scenarios that I missed over the years, for which instead I awkwardly declared a variable in a separate statement up front because I thought that that was necessary in order to refer to it from several branches of an if/else chain. Not so.

Once more unto the Wide character (U+XXXX) in substitution (s///)

I wrote very elliptically about this warning and received some helpful comments with the standard advice about how to proceed when encountering it. Except unfortunately that advice will be of no use when you encounter this warning.

Namely I should have been less cute about it and made it clear that I was specifically talking about a warning about a wide character “in substitution”. How can a s/// even possibly trigger a wide character warning, you ask? Beats me, to be entirely honest, even now, but: if you have a use locale somewhere, it turns out that it can. Because defeating that is what fixed the warning I was getting:

Wide character (U+XXXX) in substitution (s///)

There is a “use locale” somewhere in the code you are running.

Update: This is specifically in reference to warning about wide characters “in substitution”. See also the follow-up entry.

Template Toolkit’s DEFAULT is not too useful

Quoth the fine manual for Template Toolkit:

The DEFAULT directive is similar to SET but only updates variables that are currently undefined or have no "true" value (in the Perl sense).

Nice. Basically, where SET is like the = operator in Perl, DEFAULT is like the ||= operator. Quite useful! If it were, that is. Because the analogy is only superficially true.

The ordering operators

Perl has two operators, cmp and <=>, which are basically never seen outside of sort blocks.

That doesn’t mean you can’t use them elsewhere, though. Certainly sort and these operators were designed to work seamlessly together but there isn’t anything sort-specific about the operators per se, and in some contexts they can be the most appropriate solution.

An example would be some code that used to go (almost exactly) like this:

my ( $maj, $min, $rel ) = split /[.]/, $version;
my $version_ok = $maj > 3 || (
    $maj == 3 && ($min > 6 || (
        $min == 6 && $rel >= 8
    ))
);

Basically, this is a check that the version number of something with a three-component dotted version is at least 3.6.8. As expressed in the code, for both the major and minor version there are two cases where they might be accepted: if the component is above a given threshold then the whole version is acceptable with no further questions, but if equal to that threshold, the version might still be acceptable depending on the value(s) of the lesser component(s). At the same time, if the value is below that threshold, the version is unacceptable, no further questions necessary. So there are three cases, two of which can succeed, and two of which require no further checks.

This is exactly what the <=> operator offers: if you ask $maj <=> 3 you will get 1 if the major version is above 3 (immediate success), -1 if it is below 3 (immediate failure), and 0 if it is equal to 3 (success to be determined from the lesser components). Because 0 is false, you can chain such comparisons with logical-or, until one of them gives an immediate success/fail – as of course we are used to doing in sort blocks.

And so this is what the code looks like now:

my ( $maj, $min, $rel ) = split /[.]/, $version;
my $version_ok = ( $maj <=> 3 || $min <=> 6 || $rel <=> 8 ) != -1;

The expression in parentheses has each component of the actual version on the left and the respective component of version 3.6.8 on the right, and will stop at the first comparison that gives a definite answer. The final != -1 then checks that the answer was not “the right side is greater” at the final comparison – i.e. “the given version is not below version 3.6.8”.

Making local::lib real easy to use

In bash, at least.

Just paste this (on your command line or into .bashrc or wherever else you want):

perl-lib() { eval "`perl -M'local::lib @ARGV' - "$@" 0<&-`" ; }

Then you can just say things like this:

$ perl-lib ~/locallib/foo
$ perl-lib --deactivate ~/locallib/bar
$ perl-lib --deactivate-all

… instead of having to type stuff like this:

$ eval "`perl -Mlocal::lib - --deactivate ~/locallib/bar`"

… or even, as you would have had to in old versions of local::lib, this awfulness:

$ eval "`perl -Mlocal::lib=--deactivate,$HOME/locallib/bar`"

Note that the shell function will work irrespective of local::lib version.

Update: I originally posted this with a 1<&- redirect in the function, which closes STDOUT. What I actually wanted to do was close STDIN, of course.

Speaker for the dead

I just retired one of my modules. I want to make a note here of the preservational ritual I followed, to invite others to consider doing the same:

Installing Term::ReadLine::Gnu on OS X, the easy way

If you try to install Term::ReadLine::Gnu on Mac OS X, you will ordinarily run into this unpleasantry from the Makefile.PL (which will likely end up in such as ~/.cpanm/build.log):

The libreadline you are using is the libedit library. Use the GNU Readline Library.

Here I will assume that you are using Homebrew and have installed GNU Readline:

brew install readline

Even so, you will get this error. This is because of how Homebrew installs Readline. Since OS X ships libedit as libreadline, Homebrew tries to avoid conflicts with system software by installing Readline as “keg-only” software – that is, it’ll install it within its package-managed filesystem hierarchy beneath /usr/local/Cellar, but it won’t link the libraries into /usr/local/lib, so that they won’t be visible to software that isn’t explicitly linked against it.

There is an easy and obvious way around this:

brew link --force readline
cpanm Term::ReadLine::Gnu
brew unlink readline

This makes Homebrew link the shared libraries into /usr/local/lib – which Homebrew ordinarily refuses to do for packages marked keg-only, like Readline is, hence the scary --force. Of course, all said and done, we unlink the package again straight away, so as to not cause conflicts. By doing this, Makefile.PL run from cpanm picks them up to link. We can confirm this:

otool -L ~/perl5/perlbrew/perls/**/Term/ReadLine/Gnu/Gnu.bundle

Its output should include something like the following line:

/usr/local/opt/readline/lib/libreadline.6.2.dylib (compatibility version 6.0.0, current version 6.2.0)

This /usr/local/opt is another Homebrew-ism:

$ ls -l /usr/local/opt/readline
lrwxr-xr-x  1 user  admin  24 14 Jul 04:53 /usr/local/opt/readline@ -> ../Cellar/readline/6.2.4

Note well that I actually have Readline 6.2.4 installed!

$ brew info readline | head -1
readline: stable 6.2.4

That means doing it this way is not only easier than the hard way found elsewhere on the web (here or there), it even allows me to brew upgrade readline without breaking Term::ReadLine::Gnu.

Making DBI’s type info data usable for its quote method

I’m putting this here mostly as future copy-paste fodder:

my %sql_type = do {
    my $ti = $dbh->type_info_all;
    my $tidx = shift @$ti;
    my ( $n, $t ) = @$tidx{qw( TYPE_NAME SQL_DATATYPE )};
    map {; uc $_->[$n], $_->[$t] } @$ti;
};

Now you can say things like $dbh->quote( $latitude, $sql_type{'DOUBLE'} ). This is useful in situations where you cannot use placeholders, e.g. when generating some kind of fixture.sql file from CSV data.

A little “state” idiom

{
    # ... something that needs to be done twice ...

    if ( not state $first_iteration_done++ ) {
        # ... something that must only happen after the first time ...
        redo;
    }
}

In general, some form of “if state $flag” can be used as a “have I been here?” idiom that avoids the need to mention the flag’s identifier anywhere else. Without state, one must repeat oneself some distance away at least to declare the flag in whichever outer scope has the appropriate lifetime.

About Aristotle

user-pic Waxing philosophical