Template Toolkit’s DEFAULT is not too useful
Quoth the fine manual for Template Toolkit:
The
DEFAULT
directive is similar toSET
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