freemarker-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Denis Bredelet <>
Subject Re: [FM3] Basic treatment of null VS missing
Date Sat, 03 Mar 2018 17:24:43 GMT
Hi Daniel,

> So, here's my plan for FM3.
> The template language will know null, and it's different that from
> "missing". I think so far everyone who has used FM2 heavily will
> agree.


> On the API and implementation level we will have a singleton,
> TemplateNullModel.INSTANCE, that stands for things that do exist but
> has null value, while a plain null value indicates that the thing is
> missing. For example, Environment.getVariable(name),
> TempateHashModel.get(key), and TemplateSequenceModel.get(index)
> returns null if the subvariable is missing, and
> TemplateNullModel.INSTANCE if it's present but stores a null value.
> Also in TemplateDirectiveModel and TemplateFunctionModel the
> `TemplateModel[] args` argument of execute(...) will contain null if
> the argument was omitted on the caller side (like in f() the 1st
> parameter is omitted), but TemplateNullModel.INSTANCE if it the
> argument was present, but has null value on the template language
> level (like in f(null) or f(obj.willReturnNull())).
> The above can be confusing, as null on the template language level is
> TemplateNullModel.INSTANCE on the API level, and null on the API level
> is "missing" on the template language level... So when I write null,
> pay attention to if it's null on the template language level, or null
> on the API/implementation level.
> On the template language level we should have this:
> - null is a keyword. "missing" (aka "undefined") is not a value on the
>  template language level, and there's no keyword for it.

That is not required. If null is needed in the template you can just add it to the model,
no need for a keyword.

By the way how do you add null to the model? Do you use TemplateNullModel.INSTANCE or just

> - <#assign x = null> is legal. <#assign x = noSuchVariable> is an
>  error, as noSuchVariable is "missing", and not null.


> - Reading x doesn't fall back to higher variable scopes if x is null,
>  only if x is missing.


> - javaObject.someMethod() evaluates to null when the Java someMethod
>  method returns null in Java. But if someMethod() has void return
>  type, javaObject.someMethod() evaluates to "missing" in the
>  template. (These two cases were indistinguishable in FM2 templates.)


> - bean.noSuchProperty (where you don't have a getNoSuchProperty()
>  method in the bean class) is "missing", while bean.nullProperty
>  (where you do have a getNullProperty() method in the bean class, but
>  it returns null) is null.


> - javaMap.noSuchKey is null, similarly as Map.get("noSuchKey") returns
>  null in Java. So we don't differentiate the case when the Map
>  doesn't contain the key from the case when the Map contains the key
>  and the associated value is null. While we could do that using
>  Map.contains(key), I believe that would be highly impractical, as
>  you rarely put null values into to Map-s to ensure that all possible
>  keys are present. Compare that with beans, where the "keys" are
>  defined by the bean class statically.


> - importNamespace.noSuchVar is "missing". This will make more sense
>  after #var/#set was introduced, and so variables always will be
>  declared on parse time (not just on runtime), so the set of
>  variables in a namespace is fixed.


> - [#var x] (future feature) initializes x to null.

Are we talking about « template null » or « API null » here? I expect the first.

> It's hard to evaluate the above if you don't known how the
> null/missing handler operators
> (
> will react to "missing". That was touched in
> but going into details would derail discussion. But the basic idea is
> that exp!default only handles null, not "missing". Why? Let's say you
> write ${user.realNaem!'Not provided'}. As you see you expect the
> realName to be null, and also you managed to write realNaem instead of
> realName (a typo). If exp!default handles "missing", then this mistake
> is hidden by it, while if it only handles null then you get an error
> (as there's no getRealNaem() method in that bean) as desirable.

Makes sense.

> Now, there are some rough edges in this idea, like what if you write
> user.phoneNumber, and while getPhoneNumber() dies exists in class
> EmployeeUser, it doesn't exist in CustomerUser... so in case user in
> the data-model is a customer, ${user.phoneNumber!'Not provided'} will
> not save you. If that can't be addressed on the data-model level (like
> abstract class User is annotated with @ValidPropertyNamesFrom({
> EmployeeUser.class, CustomerUser.class })), there's the possibility
> that there will be something like exp!!default that handless both null
> and "missing", or that the right compromise will be if exp!default
> handle both after all. But, I can proceed without deciding this part
> right now.

Maybe in that situation you would use a bean wrapper that maps non-existent keys to null instead
of missing.

> Another interesting aspect is parameter defaults in macro/function.
> Clearly, an argument that's "missing" is immediately an error (unlike
> in FM2), so defaults are irrelevant in that case. But what will happen
> if you have <#function f(x=0)>, and then you do f(null)? The parameter
> was not omitted, so will x be 0 or null? I think parameters should
> have an attribute that tells if it should accept null-s or not (kind
> of like "not null" constraint in databases). If we accept that idea,
> then the answer to the question is that if x has a "not null"
> constraint then x will be 0, but if x is nullable then x will be null
> in the example case (because the default value is not applied). BTW, I
> believe the best would be if macro/function parameters by default has
> "not null" constraint, and you explicitly indicate if want to allow
> null-s (e.g. `<#function f(x{nullable}=0)>`).


— Denis.

> -- 
> Thanks,
> Daniel Dekany

View raw message