freemarker-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Daniel Dekany <ddek...@apache.org>
Subject Re: [FM3] Basic treatment of null VS missing
Date Sat, 03 Mar 2018 20:04:28 GMT
Saturday, March 3, 2018, 6:24:43 PM, Denis Bredelet wrote:

> 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.
>
> Yeah!
>
>> 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.

That would be like if you have to add "true" and "false" to the
data-model as well. People can't reliably use something if whether
it's there depends on what was added to the data-model, and if
something accidentally hides it. Or consider examples on the Internet,
were you always had to start with "assuming you have
TemplateNullModel.INSTANCE with name 'null' in the data-mode".

> By the way how do you add null to the model? Do you use
> TemplateNullModel.INSTANCE or just null?
>
>> - <#assign x = null> is legal. <#assign x = noSuchVariable> is an
>>  error, as noSuchVariable is "missing", and not null.
>
> ok
>
>> - Reading x doesn't fall back to higher variable scopes if x is null,
>>  only if x is missing.
>
> Great!
>
>> - 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.)
>
> Yes
>
>> - 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.
>
> ok
>
>> - 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.
>
> Fine
>
>> - 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.
>
> Good!
>
>> - [#var x] (future feature) initializes x to null.
>
> Are we talking about « template null » or « API null » here? I expect the first.

Template null. API null would mean that x is still "missing" after
<#var x> (and so reading x will fall through to the higher scopes),
which would be counter intuitive, as we have just stated that x is a
variable. So the variable itself is not missing, and the closes value
to "not yet known" is null. Anyway, I believe #var/#set wasn't
discussed yet, and I would avoid it in this thread. I plan to discuss
that later. For now, it suffice knowing that #var declares a variable
in a certain scope, and typically you will also initialize it there
explicitly, like <#var x = 1>. But sometimes you got a situation like:

  <#var x> <#-- We don't know x yet, we just widen its scope -->
  <@someVarScope>
    ...
    <#set x = 123> <#-- Now we know x -->
    ...
  </@someVarScope>
  ${x} <#-- x is still in scope -->

>> It's hard to evaluate the above if you don't known how the
>> null/missing handler operators
>> (https://freemarker.apache.org/docs/dgui_template_exp.html#dgui_template_exp_missing)
>> will react to "missing". That was touched in
>> https://lists.apache.org/thread.html/f520220aefc5064030f8674a1cd482b6469797ff74ad2bdd3c0a1b9c@<dev.freemarker.apache.org>,
>> 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.

You could. Like if the data model itself (the root) used to be Map,
and you switch to using a bean, it's likely that you want to do that,
although probably only for that single bean.

>> 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)>`).
>
> ok
>
> — Denis.
>
>> -- 
>> Thanks,
>> Daniel Dekany

-- 
Thanks,
 Daniel Dekany


Mime
View raw message