Skip to content

Loosened Semantics

Jonathan Revusky edited this page Jun 16, 2024 · 5 revisions

In FreeMarker 3, in what is now the default mode of strict variable definition the semantics were tightened up significantly. However, on other fronts, things were actually loosened up somewhat.

Basically I (revusky) finally decided that most any object would be treated as a string in the appropriate context -- simply by calling the Object.toString() method on it. In certain cases, such as numbers and dates, when we have locale-specific information, of course, that is used if it is available, but if not, we will just fall back to an object's toString() method to display it.

Earlier versions of FreeMarker were quite insistent that in a boolean (true/false) context, you really needed to have a legit boolean value, so it disallowed certain typical syntactic sugar, so you had to write:

[#if myList?size>0]
    something here
[#else]
    something else
[/#if]

Now, an empty list, in the above context is just taken to be the same as false, so you can just write:

#if myList
    something here
#else
    something else
#endif

So, if myList is an empty list or a java null, we take it to be the same as false. On a strictly logical level, this is somewhat incorrect, and I agonized over this, but finally, I just had the sense that notational convenience has a lot going for it, so allowing people to write this in the more terse manner above finally, on balance, was a good option.

A zero-length string is taken to be false as well. Again, not quite correct logically, but convenient notationally. Any other scalar (including numbers and dates) is taken to be a true.

More things are permissible on the left-hand side of an assignment

Previously, you could only put an identifier on the left-hand side of an assignment, as in:

 [#assign foo = "bar"]

The target of the assignment could only be a top-level variable defined either in the template's namespace or a local variable, i.e. defined within the scope of a macro call.

Now, you can write:

#set foo.bar = "baz"

And this maps onto the Java code foo.put("bar", "baz") assuming, of course, that foo is an instanceof java.util.Map. If not, it will try to use the typical Java beans naming pattern and thus try to call:

 foo.setBar("baz")

Of course, if there is no appropriate setBar method, then an exception is going to be thrown.

Likewise, you can set an element in a list, as in:

 #set foo[n] = bar

which, of course, maps onto list.set(n, bar);

I might as well admit that I (revusky) agonized over this (probably long-due) enhancement because it does deviate from the MVC paradigm, which the 'M', the model, should typically be treated as immutable. However, finally, I decided that since we're (at least by default) exposing the Java API, and you can certainly write:

#exec foo::put("bar","baz")

we might as well allow the more natural syntax of:

#set foo.bar = "baz"

Another little wrinkle on this is that FreeMarker has long allowed things like:

[#import "someLib.ftl" as lib]
[#assign foo = "bar" in lib]

So you could assign the foo variable in another scope. And, in fact, since the scope object is just a HashMap, the above would be equivalent to: lib.put("foo", "bar");

Of course, the above construct was really only necessary, because it was not possible to write:

[#assign lib.foo = "bar"]

So, it is perfectly maintainable that the #set var in lib notation is superfluous and thus should be deprecated. For now, I have decided to leave it there. Existing code with macro libraries surely uses it extensively and quite possibly, in some cases, it is more clear to read. So, the bottom line is that you can still write:

#set foo = "bar" in mylib

but you can also write:

#set mylib.foo = "bar"

It's your call.