This was originally meant to be posted to the Freemarker issue
tracker, so it’s formatted as such.

----

I’ll refer to “missing values” as just “null” in this post. I hope you
don’t mind.

----

I was always awkward out by Freemarker’s null handling. This doesn’t
make sense to me:

{code:none}
<#if foo.bar.baz.qux.bah??> <#-- Only tests for null in `bah` (error
if null somewhere else) -->
    <#-- ... -->
</#if>
{code}

{code:none}
<#if (foo.bar.baz.qux.bah)??> <#-- Tests for null all the way through -->
    <#-- ... -->
</#if>
{code}

{{??}} and {{!}} don’t behave like operators generally would in
programming languages. In my point of view, operators should act
similar to functions, and there is no way to actually write a function
that behaves like {{??}} does. It seems to me that {{(expr)??}} and
{{expr??}} are completely different operators with slightly different
semantics.

Additionally, there is no way to test if only {{qux}} or {{bah}} is
null (and stop with an error if there is null somewhere else). That
is, the following doesn’t work:

{code:none}
<#if foo.bar.baz.(qux.bah)??> <#-- doesn’t work -->
    <#-- ... -->
</#if>
{code}

{code:none}
<#if foo.bar.baz(.qux.bah)??> <#-- doesn’t work -->
    <#-- ... -->
</#if>
{code}

h1. My solution

My solution is greatly inspired by [Ceylon|https://ceylon-lang.org/].

h2. Expressions evaluating to null don’t throw exception

It seems pretty stupid since you still wouldn’t be able to do anything
with null. You wouldn’t be able to store it in variables, output it,
access most built‐ins in it, access hash members in it. But this
feature would allow certain important things to be done: accessing
certain null‐specific built‐ins in it and most interestingly:
accessing built‐ins and hash members in it in a safe way.

h2. {{exists}} and {{is_absent}} built‐ins

I think that instead of having an operator exclusive to dealing with
null is completely unnecessary since Freemarker already has a
mechanism that could be used to do it: built‐ins. {{exists}} could be
used to do what {{??}} does today, and {{?is_absent}} would be its
logical negation.

h2. {{else}} built‐in

Similarly to {{??}}, it feels to me that we could reuse the much more
general‐purpose built‐ins mechanism in the place of {{!}}. Instead of
writing {{foo!12}}, one would write {{foo?else(12)}}.

h2. {{is_*}} built‐ins

Existing built‐ins that test for type (such as {{is_number}},
{{is_string}}, {{is_sequence}}) would be able to operate on null and
would always evaluate to {{false}}.

h2. {{!.}}, {{![]}}, and {{!?}}

These symbols would work as a “null‐safe” hash member access and
built‐in access. That is, {{foo!.bar}} would access the member {{bar}}
from {{foo}} unless {{foo}} is null, in which case the whole
expression will be null. The behavior is analogous for {{![]}} and
{{!?}}.

The behavior of built‐ins that _can_ operate on null (such as
{{has_content}} or even {{else}}, {{exists}} and {{is_*}}) would be
the same as the ones that can’t: if the operand evaluates to null, the
expression will evaluate to null, otherwise, it will access the
buit‐in.

Then, instead of writing {{(foo.bar.baz.qux.bah)?exists}}, one would
write {{foo!.bar!.baz!.qux!.bah?exists}}.

h2. {{exists}} conditions

In Ceylon, one can write {{if(foo, bar, baz)}} to mean {{if(foo && bar
&& baz)}}. What may seem like a trivial syntactic difference actually
allows for a more powerful feature to exist: the {{exists}} condition.
The {{exists}} condition is not actually an expression, but it is part
of the {{if}} syntax. it may be used to declare variables that can be
used inside the {{if}} body, while also checking for {{null}}. In
Freemarker, the syntax would look like this:

{code:none}
<#if exists bah = foo.bar.baz.qux.bah> <#-- Only tests for null in
`bah` (error if null somewhere else) -->
    ${bah}
</#if>
{code}

{code:none}
<#if exists bah = foo!.bar!.baz!.qux!.bah> <#-- Tests for null all the
way through -->
    ${bah}
</#if>
{code}

Multiple {{exists}} condition could be used, separated by a comma ({{,}}):

{code:none}
<#if exists baz = foo!.bar!.baz, exists bah = baz!.qux!.bah> <#--
Exposes both `baz` and `bah` -->
    ${baz} ${bah}
</#if>
{code}

{code:none}
<#-- You still wouldn’t be able to check `null == something`, since
the abscence of a value (null) doesn’t have a well‐defined value
equality -->
<#if exists bah = foo!.bar!.baz!.qux!.bah, bah == "hello"> <#-- Tests
for null and checks if `bah` is equals to `"hello"` -->
    Hello!
</#if>
{code}

h1. Conclusions

I feel like this will be a great improvement to Freemarker, making it
feel more intuitive by:

# making its operators’ behaviors more intuitive
# helping avoid repetition with {{exists}} variables
# making the language easier to learn by reusing existing mechanisms
instead of creating completely new operators to learn

Reply via email to