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