Closed determin1st closed 8 months ago
No.
There is a subtle difference in the treatment of context methods depending on whether the lambdas extension is implemented. Suppose that we have the following template (with a non-inverted section, but bear with me for a moment):
{{#meaning}}42{{/meaning}}
and that the context is the following JavaScript object:
{
meaning: function(sectionText) {
return '<b>' + sectionText + '</b>';
}
}
then a Mustache engine that doesn't implement the lambdas extension will render 42
, while an engine that does will render <b>42</b>
. The reason for this is that without the lambdas extension, the return value of a context method is considered just another value, like any other value that you might resolve from the context. With the lambdas extension, on the other hand, the return value of a context method is interpreted as template text that replaces the original content of the section.
(The distinguishing feature of the lambdas extension is not that lambdas/functions/methods are invoked in the first place. The basic interpolation, sections and inverted specs already require that they are invoked and that their return values are used. The real distinguishing feature is that lambdas/functions/methods gain additional influence over what is rendered. I didn't realize this myself until I read the specs a few times.)
Inverted sections obviously need to behave opposite to regular sections. According to the lambdas extension, a section will invoke a context method in order to establish the inner template text to be rendered. So an inverted section will NOT invoke a context method, let alone render whatever template text it might return.
The basic interpolation, sections and inverted specs already require that they are invoked and that their return values are used.
where? there is no lambdas in interpolation
and inverted
test files (only variables there). lambdas are in lambdas testfile. i don't get what you mean by that.
So an inverted section will NOT invoke a context method..
if it doesn't support lambdas, sure, it will not.. same logic may be applied to normal section (why not?), #section
will not invoke anything and will give 42
result (just because it's a truthy variable) - according to the lambda extension? i'm not getting this logic..
there is no point in compatibility with lambda and non-lambda engines by the means of making
if lambda and lambda() => content
if not lambda => content
because still, it will give different results (42
without and <b>42<b>
with)
it may be solved by implementing an option of compatibility in the engine with lambdas,
like lambdas: false
, then it will not invoke lambdas at all:
if lambda => content
if not lambda => content
and will be variables first compatible.. otherwise
if lambda and lambda(text) => content
if not lambda or not lambda() => content
is more appropriate (with lambdas).. maybe i miss something.. but it appears unlikely..
You are right to point out that there is no test for lambdas in the sections spec. However, the overview of the sections spec does prescribe that lambdas must be invoked and that their return values must be used for name resolution:
You find exactly the same prescription in the inverted spec:
As I wrote before, that is already the specified behavior without even including the lambdas extension. It is important to realize that this takes place as a step during name resolution. Name resolution must be fully complete before the engine even starts to determine what will be rendered. So in all cases (variables, sections and inverted sections, whether the lambdas extension is implemented or not), taking inspiration from your notation, the following is what happens during name resolution:
value := name from context
if value is lambda => use value() as value
Without the lambdas extension, section rendering proceeds as follows:
if value is not array =>
if value is truthy => use [value] as value
if value is falsy => use [] as value
render original section template once for each element of value
The inverted section applies the same conditions, except that it only renders the original section template if value
is empty.
With the lambdas extension, there is an additional condition before section rendering starts:
if value was obtained by invoking lambda =>
use value as raw template text that replaces original section content
render replaced section once with current context
stop here
else continue as regular section
You find that prescribed over here:
While it's left somewhat implicit, I think the appropriate opposite behavior for inverted section is as follows:
if value was obtained by invoking lambda =>
use value as raw template text that replaces original section content
render replaced section zero times with current context
stop here
else continue as regular inverted section
and as far as I can tell, the particular test that you're asking about agrees with this interpretation. Note that since "once" becomes "zero times", the inverted section effectively never renders a lambda, so the Mustache engine can optimize the lambda invocation away.
now i get it, i missed the object.method call.. let me examine the logic.. will return to it today..
Ooke, let's clarify some wording..
lambda
is an anonymous function, called without specified object/context.
method
is a function called within specified object/context.
Take a look at this JavaScript object (last lambdas test):
data:
static: 'static'
lambda: function(txt) {return false}
when the name lambda
is resolved, as described in the spec,
the data.lambda(text)
should be invoked because it's a property of the data
object.
This objects your statement about not doing a call.
Same goes with JavaScript arrays, they are considered objects. Which is false for PHP.
My opinion is that the name resolution should be separated from the knowledge of variable/section/inverted. This way, no object.method call will be made, only traversal. When the object.method is found as the last name (for example {{#lastName}}
or {{#here.goes.lastName}}
),
it should be wrapped with it's context and passed as a result value.
Later, it may be called or not called.
It was already prescribed to call them. The only reason i see for no-call is the section check first:
value := resolveFromContext(name)
if invertedSection
if value
=> render empty
otherwise
=> render non-empty
otherwise
=> handle normal section
vs falsy check first:
if not value := resolveFromContext(name)
if invertedSection => render non-empty
otherwise => render empty
if isFunction(value)
if isWrapped(value)
value := unwrap(value)(text)
else
value := value(text)
if invertedSection
# ...
im really lost with this spec :)
Please excuse me for quote-sniping you a little.
lambda
is an anonymous function, called without specified object/context.method
is a function called within specified object/context.
Depending on the language, a lambda might be a closure, in which case it might actually be bound to a particular object/context. I've been carefully avoiding the word "lambda" alone when referring to callable functions, because of the wild variety of possible interpretations. With the Mustache spec in mind, "lambda" should be mostly thought of as a feature of the Mustache templating language, rather than a feature of a programming language.
As you probably already realized, a lambda in Mustache will be a method in the underlying programming language most of the time.
Take a look at this JavaScript object (last lambdas test):
data: static: 'static' lambda: function(txt) {return false}
when the name
lambda
is resolved, as described in the spec, thedata.lambda(text)
should be invoked because it's a property of thedata
object.
So far, I think I understand what you mean, ...
This objects your statement about not doing a call.
but here, I'm losing you. Do you mean that there is a conflict with what I've written so far? I'm not seeing it.
Same goes with JavaScript arrays, they are considered objects. Which is false for PHP.
I fail to see how this is relevant.
My opinion is that the name resolution should be separated from the knowledge of variable/section/inverted. This way, no object.method call will be made, only traversal. When the object.method is found as the last name (for example
{{#lastName}}
or{{#here.goes.lastName}}
), it should be wrapped with it's context and passed as a result value. Later, it may be called or not called.
In that case, I think the spec agrees with you. Methods earlier in the chain still need to be invoked at this stage, though. In your example above, if here.goes
is a method, then it must be invoked in order to obtain an object that has lastName
as a member.
Why not call?
It was already prescribed to call them.
Here is the full algorithm that the spec prescribes (as far as sections and inverted sections are concerned):
frame, value := resolveFromContext(startingContext, name)
if isFunction(value)
if lambdasImplemented
finalValue := startingContext
if invertedSection => renderedSectionTemplate := ""
otherwise => renderedSectionTemplate := frame.value(originalSectionTemplate)
otherwise
finalValue := frame.value(originalSectionTemplate)
renderedSectionTemplate := originalSectionTemplate
otherwise
finalValue := value
renderedSectionTemplate := originalSectionTemplate
if isArray(finalValue) => contextList := finalValue
otherwise
if truthy(finalValue) => contextList := [finalValue]
otherwise => contextList := []
if invertedSection
if contextList empty => render renderedSectionTemplate with startingContext
otherwise => render empty
otherwise
for renderedContext in contextList
render renderedSectionTemplate with renderedContext
according to your code, when isFunction
and lambdasImplemented
the assignment if invertedSection => renderedSectionTemplate := ""
and later isArray
check with context manipulation don't do any good, it will always be render empty
.. because template set empty.
take a look at my interpretation: https://github.com/determin1st/sm-mustache/blob/99776b748b0b49c8c1dc0b3d4ac28c31233f7f85/mustache.php#L537-L551
un-commenting the block (after the falsy check) will comply with the lambda spec (this issue), but, it means that functionality will be removed, not added.
as i understand, the main reason why "lambdas" was made - is to replace/substitute section's content, which inverted section can't do by means of common sense, what they can, is to specify a flag (as a call result), with this test, the flag is not respected.
so the question "why" is about the flag being needed or not.
according to your code, when
isFunction
andlambdasImplemented
the assignmentif invertedSection => renderedSectionTemplate := ""
and laterisArray
check with context manipulation don't do any good, it will always berender empty
.. because template set empty.
True. As I wrote before, there is a lot that can be optimized away. My own implementation does that, too. I was just representing the un-optimized algorithm as it is described in the spec.
take a look at my interpretation: https://github.com/determin1st/sm-mustache/blob/99776b748b0b49c8c1dc0b3d4ac28c31233f7f85/mustache.php#L537-L551
un-commenting the block (after the falsy check) will comply with the lambda spec (this issue), but, it means that functionality will be removed, not added.
I can't really comment on your implementation without giving it a lot of study first, but I will take your word for it.
as i understand, the main reason why "lambdas" was made - is to replace/substitute section's content, which inverted section can't do by means of common sense, what they can, is to specify a flag (as a call result), with this test, the flag is not respected.
so the question "why" is about the flag being needed or not.
I wasn't around when the spec was written. With that out of the way, there are lots of subtle variations possible when defining the semantics for a formal language (as you illustrated with your comments). The authors need to take care to pick a combination of variations that is (a) useful, (b) practically implementable and (c) consistent.
The alternative that you are suggesting, where inverted sections are basically exempt from the lambdas extension, is arguably useful. As you wrote, doing so would allow inverted sections to make a yes/no rendering decision based on what the named function returns. However, that wouldn't be consistent. Consider the following example:
{{#myLambda}}yes{{/myLambda}}{{^myLambda}}no{{/myLambda}}
First consider the case that myLambda
is a function that returns a new template. In that case, the inverted section will obviously never render unless the returned template is the empty string. What do you believe should be rendered when the returned template is, in fact, the empty string?
Next, consider the case that myLambda
is a function that returns a boolean. According to what you are suggesting, the template will render "no" if it returns false
. However, if the function returns true
, then what do you believe should be rendered?
Finally, consider the case that myLambda
doesn't exist. In that case, at least, we can probably agree that "no" should be rendered.
The algorithm in the spec avoids the contradictions that I described above. If myLambda
is a function, then it basically counts as a single truthy value, so we always render the normal section with whatever it returns as a template. Otherwise, the lambdas extension doesn't apply, so we can follow the other rules on whether to apply the normal section or the inverted section. It's also useful; we can use inverted sections as a "fallback option" for when a lambda we wanted to use isn't available. I think the authors made a pretty good choice at the time.
{{#myLambda}}yes{{/myLambda}}{{^myLambda}}no{{/myLambda}}
0) myLambda returns 'something new'
string => 'something new'
(first is replaced, second is truthy negated, empty)
1) myLambda returns ''
empty string => no
(first section is replaced with empty, second is falsy negated, rendered)
2) myLambda returns true
boolean => yes
(first is true, rendered, second is true negated, empty)
2) myLambda returns false
boolean => no
(first is false, empty, second is false negated, rendered)
3) myLambda is null
=> no
(first is falsy, empty, second is falsy negated, rendered)
what is wrong/contradicts here?
myLambda returns
'something new'
string =>'something new'
(first is replaced, second is truthy negated, empty)myLambda returns
''
empty string =>no
(first section is replaced with empty, second is falsy negated, rendered)
This is contradictory. In the first case, you are interpreting the returned string as a new template. In the second case, you are effectively interpreting the string as a boolean. In order for these two conditions to be consistent with each other, you'd either have to render yes
in the first case or the empty string in the second case.
yes, because the nature of {{^section}}
is the if not
expression, it will interpret any returned value as a flag. with current spec it will always be true (empty):
'something new'
=> 'something new'
''
=> ''
better?
means that template {{#myLambda}}yes{{/myLambda}}{{^myLambda}}no{{/myLambda}}
equals to {{#myLambda}}yes{{/myLambda}}
- this is a contradiction to my taste.
'something new'
=>'something new'
''
=>''
better?
These two cases are no longer contradictory, but now case 1 is contradictory with both case 2 (true
used as a boolean instead of as a replacing template) and case 3 (falsy return value leading to "no" being rendered in one case but not in the other). Case 0 is also contradictory with case 2.
means that template
{{#myLambda}}yes{{/myLambda}}{{^myLambda}}no{{/myLambda}}
equals to{{#myLambda}}yes{{/myLambda}}
- this is a contradiction to my taste.
Those templates are not equivalent; they still give different results if myLambda
is undefined.
shouldn't it be (for inverted)
plus (maybe)