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.

You see, DEFAULT in Template Toolkit does not short-circuit: it evaluates the right-hand side before it checks whether the expression on the left-hand side is true. If you deparse the compiled template it is obvious why:

SET foo = bar ;
# compiles to: $stash->set('foo', $stash->get(['bar']));

DEFAULT foo = bar ;
# compiles to: $stash->set('foo', $stash->get(['bar']), 1);

It turns out that DEFAULT is not just similar to SET, it is literally the same thing. And as you can see, by the time the set method runs and gets to examine the value in foo, the get method will have already run and returned. In the case of SET, the order in which these calls happen makes no difference, because foo will be set either way. But for DEFAULT the order most certainly matters.

In the example so far this results in at most a bit of wasted computation, but it becomes an actual problem in cases where short-circuiting actually affects the outcome, e.g.

DEFAULT foo = lookup_or_die( bar ) ;
# compiles to:
# $stash->set('foo', $stash->get(['lookup_or_die', [$stash->get('bar')]]));

Here you might have hoped to be saying “if foo wasn’t passed, populate it from a lookup on bar” but what you actually ended up saying is “do a lookup on bar and then put that value in foo unless something is in there already”. So if bar contains a value for which you know the lookup fails, and you were hoping that passing a value for foo would allow you to sidestep that, your hopes will be dashed. To say what you were hoping to say, you have to be explicit:

SET foo = lookup_or_die( bar ) UNLESS foo ;
# compiles to:
# unless ($stash->get('foo')) {
#   $stash->set('foo', $stash->get(['lookup_or_die', [$stash->get('bar')]]));
# }

The bottom line is that DEFAULT is only truly fit for use with constants on the right-hand side. An expression without side effects on the right-hand side will work, but will do wasted work, which may or may not be significant. A right-hand side expression with any sort of side effect likely means you just created yourself a bug.

(This is mostly a note to self, written in hopes that doing so will make the lesson stick.)

Leave a comment

About Aristotle

user-pic Waxing philosophical