-
Notifications
You must be signed in to change notification settings - Fork 3
Strict Vars
In older versions of FreeMarker, the way you set a variable in the template was with the #assign
directive. You could also set a local variable in a macro using #local
. Those directives still work if you have explicitly turned off strict variable definition. Specifically, you do that for a single template by putting:
#ftl legacy_syntax
at the top. This can also be done globally from Java code via:
Configuration.getDefaultConfiguration().setLegacySyntax(true);
Without that, you need to use the newer #var
and #set
directives, which I shall describe now.
Has it ever occurred to you how fragile shell (or .bat) scripts tend to be? For example, I suppose most people reading this are Java hackers. Suppose you write some script to launch a Java app, with something like:
CLASPATH=~/myjavalibs:$CLASSPATH
What happens in the above is that CLASPATH
is misspelled, so instead of redefining the existing CLASSPATH
variable, prepending ~/myjavalibs
to it, this line in effect declares a new variable called CLASPATH
and the existing CLASSPATH
variable remains unchanged. Then, of course, the script doesn't work as intended, but it can be not terribly obvious why.
What the the #set
and #var
directives do is they create a clear conceptual distinction between declaring a variable and setting its value. Admittedly, this is more typical of real programming languages, but I think it has value in this kind of templating language.
After all, the same problem exists in FreeMarker templates. Suppose you have an existing variable called name
but you write:
[#assign Name = client.name]
and, rather than redefining the existing name
variable, it creates a new variable called Name
and sets that. Using #var/#set
you need to declare the variable with the #var
directive before using it. So, suppose you define a macro like so:
#macro myMacro
#local name = client.name
...
/#macro
In the above, there is effectively a local variable called name
declared and set. The preferred way to express this now is:
#var name
#set name = client.name
So if you misspell the variable, you will get an error. So, if you later write:
#set Name = "Mr. " + client.name
you will hit a clear error since the variable name that was declared was name
, not Name
. With the older disposition, the above error just passes through. But now it gets caught.
Note also that the lines:
#var name
#set name = client.name
can be written more tersely (and this is surely how most people would write it!) as:
#var name = client.name
So, in fact, using the newer #var/#set
is not more verbose than the older #global/#local
. (Quite the contrary, since #var
and #set
are shorter than #assign
or #local
! And, of course, in conjunction with the newer bracket-free syntax this tends to make templates in the newer style significantly shorter.)
When you declare a variable, it is taken to exist in the block where it is defined. So, if you have:
#if someCondition
#var something = foo.bar
...
${something}
/#if
${something}
In the above, the final ${something}
will be an error since the something
variable only exists in the block in which it is defined. So the above will blow up except on the off chance that this scope (the one that contains the if block) contains a something
variable. Another way of expressing the concept is that if you want the something
variable to be visible, it needs to be defined outside the if block, as in:
#var something
...
#if someCondition
#set something = foo.bar
...
${something}
/#if
${something}
Regardless, the bottom line is that you typically define a variable as locally as possible, so there is much less unintentional clobbering of existing variables via name clashes. Well... more like in a real programming language!
I initially considered making legacy_syntax
true by default, to ease people's transition, but I decided finally, that I prefer to maximally encourage people to use the newer syntax. Not only is it less typing, but it should lead to more robust results!