This is a good conversation. I want to try to summarize to help push
towards some conclusion. The following combines a whole pile if ideas from
various people.
First, note that I am trying to look at this mainly from the point of view
of the template author, and since we are talking about VTL behavior for the
most part, I think that's appropriate.
There are three possibilities declared as needed so far for the 'state' of
an reference :
A) There is a valid object in the context for a given key.
B) There is a null in the context for a given key.
C) The key isn't in the context.
And we want :
1) In the template, to be able to know if a reference doesn't have a
'value'. Note that this is different that they way I have previously
expressed this, namely "Is a reference in the context?". I think the goal
is the same to the template author, in that either way, you can't do
anything, but 'having a value' may help clear the thinking.
2) We want to be able to deal with #set($foo = something null ) in a
consistant way, and the aftermath able to be handled in the template. For
instance, a method on on object in the context returns 'null'.
3) We want to be able to deal with #set( $foo = something invalid) in a
consistant way, and able to be handled in the template. For instance, the
RHS isn't a valid reference, it's null, or $foo.bar is just invalid -> bar
isn't a method or key.
4) We want to handle the app side putting null keys and values into the
context w/o throwing our hands up and saying 'let the implementation decide'
or having to sprinkle try{} blocks everywhere.
5) We want the chained contexts to be handled in a reasonable way. I
personally feel strongly that to keep things simple and predictable when
chaining multiple Context implementations, the chained context should be
guaranteed not to be altered - it's read-only. (Useful property for
frameworks that might have a pool of pre-filled tool contexts, for example.
And if the template is altering the context, constraining it to the local
level and letting the app handle it seems the best to me.)
One approach would be to make something referred to as 'NULL'. Note this
isn't the Java 'null', but something that combines B & C above. I would
envision as a singleton Object defined in AbstractContext or elsewhere that
would be placed into the context in place of null, and then would be used
for comparison. It would :
- for #1 above, be available for equality comparison in VTL a la #if ($foo
== NULL) : which asks "Does $foo have a valid value". We remove the
question "Is it in the Context?" because it's meaningless to a template
designer. I mean, if there is a value for this reference, we can do
something with it. If not, it doesn't matter if it's a null valued key in
the context, or not in there. The results are the same. For debugging, we
should have appropriate log messages generated both from the app side using
the Context interface and the template side #set() directive so you can
figure out why something == NULL
- for #2 above, would handle the case when we do a null assignment in a
#set(). Internally, we would just put the NULL object into the context for
that reference, and log it if in debug mode (or whatever)
- for #3, the same thing - we would place the NULL into the context when we
had an invalid RHS, and log of course.
- for #4, same thing
- for #5, we preserve the idea that chained contexts are immutable (if we
decide we want that), and by putting NULL into the 0th or local context, we
would functionally 'remove' the chained value so it won't be accessable.
because we mask it : the local get() returns the NULL object.
This would be a useful feature with chaining : a framework could fill a
context with a complete toolset, and then a permission layer/manager could
chain that context and 'mask off' the tools that aren't allowed for a given
user/session/whatever.
What's missing?
More inline :
Christoph :
> Jose is pointing to a problem with the dropping using chained contexts.
> So maybe the keeping the Context API simple is a *too* hard limitation.
>
> I originally said "dropping would work for me", but this does not
> seem to apply for all applications. Vel should be simple but general
> enough to avoid contradictions.
I agree 100%. We should never limit generalization. Does the above handle
it?
> Note that I'm not from the "no-null lobby", but am a "NULL friend" :)
> The only case I would rather see a NULL value is when I want to debug
> the context and list the "keys = primitive-values (or class-name)".
> Here it is usefull to see that foo exists and is invalid: "foo = NULL".
Well, that seems to be a java-side function rather than template side.
There is a keys() method to Context, so you should be alble to get an array
of keys. And then you can do a #if($foo == NULL) on the template side.
> Now for the context chaining it will be necessary to differentiate
> between a no-value and no-definition states. So there is a reason
> for NULL values (Hi NULL friends :).
I don't think we need to differentiate in the template. Logging them, yes.
So the concept of a NULL would work if it covered both. As the template
author, you just want to know if it's usable, right?
> Will VMs use context chaining? If so, will a macro be able to
> update higher level contexts? I guess the best way to go is to have
> a TransparentContext, where updates affect the parent contexts;
> and a LocalContext (= VelocityContext?) instatiating a chaining by
> a #local directive (as proposed in another thread) that makes all
> #sets only affect the local one. Then safe VMs should allways
> contain a #local...#end, which can be left away for speed purposes
> and/or when it does not do any dangerous sets.
The use of chained contexts in the current impl is transparent. Anything
accessing the context will access the values in a chained context (layer >
0) if the 0th layed doesn't have anything for that key.
I don't understand the need to update a higher level context. I think it's
invasive, and really complicates the issue. I think the real use for these
things is allowing thelayering of tools and data in a clean, safe,
recoverable way. If the template needs to update the context, constraining
those alterations to the local context (0th level) seems fine. That seems
like an app architecture issue to handle that, keeping the Vel chained
context idea simple and safe.
> > > Well this is exactly my point. Christoph's solution of simply removing
the
> > > context entry does not work. Why?, because it would expose the old
value
> > > after the set operation which, I think is the more confusing behavior
we can
> > > have.
> >
> > Hm. It would expose a value in a chained context if it exists.
> Well, who defines which context implementation is in effect? It seems that
> the AbstractContext shall allow NULL values, and the actual implementation
> should refuse it if not proper; or eventually propagate it to the parent
> context if it cannot handle it locally (after a local remove).
Well, the problem I see there is the utter confusion that results if you
have multiple context classes with mixed null support.
So I think that we should choose one or the other. And if we have a
solution like above (it doesn't have to be that) that keeps it clean and
simple for the template designer, keeps the behavior consistant and
deterministic, and handles all the issues so far proposed, then supporting
NULL is a decent way to go.
[SNIP]
> > Hm. I agree that there is a difference, and to some degree it's
> > important. I wonder, though, if its more important to keep things
> > simple for the template creator. You do quite advanced templates. I
> > don't think the average user would come close to what you come up with.
>
> The template creator does not see the difference.
> If he cares he could implement somehow a containsKey (e.g. #ifdefined)
> to check the difference between a no-value and a no-definition.
there is a containsKey() in the Context interface that should be supporrted,
so the java programmer has a way of checking.
If we do implement NULL and allow #if( $foo == NULL) then the template can
also figure it out. I don't though see that it's terribly important to
distinguish between 'null value' and 'not in the context' on the template
side of things. Either way, you can't do anything with it in the context,
and the log can tell you what is going on.
geir