You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@jakarta.apache.org by Apache Wiki <wi...@apache.org> on 2010/05/03 18:09:17 UTC
[Velocity Wiki] Update of "MacroEvaluationStrategy" by SergiuDumitriu
Dear Wiki user,
You have subscribed to a wiki page or wiki category on "Velocity Wiki" for change notification.
The "MacroEvaluationStrategy" page has been changed by SergiuDumitriu.
The comment on this change is: Initial version.
http://wiki.apache.org/velocity/MacroEvaluationStrategy
--------------------------------------------------
New page:
>>From [[http://en.wikipedia.org/wiki/Evaluation_strategy|Wikipedia]]: In computer science, an evaluation strategy is a set of (usually deterministic) rules for evaluating expressions in a programming language. An evaluation strategy defines when and in what order the arguments to a function are evaluated, when they are substituted into the function, and what form that substitution takes.
Looking at the various standard evaluation strategies (see http://www.knowledgerush.com/kr/encyclopedia/Call-by-something/ and http://en.wikipedia.org/wiki/Evaluation_strategy ), none of them applies to Velocity. It's a mix between call by macro expansion, call by sharing, call by value and other behaviors.
In non-strict mode, Velocity macros work mostly as call-by-macro expansion internally, with call-by-sharing external effects, but affected by automatic assignment of global variables when formal parameters can't be assigned (using a non-lvalue argument as an lvalue). We could call this ''relaxed call by lazy sharing''. This means that:
* '''Lazy evaluation''': actual parameters are not computed (reduced in lambda-calculus semantics) until they are actually used. For example, calling a macro with `#someMacro($aList.add('a value'))` will not execute the `add` call until the formal parameter of the macro is actually used, which means that the call might not get executed at all.
* '''Call by macro expansion:''' the actual parameters are computed each time the formal parameter is used. This means that in the previous example, the `add` call will be executed each time the formal parameter is used, possibly adding the value to the list several times. ''This is the behavior of C macros.''
* '''Call by sharing:''' the method arguments are not passed by reference, meaning that trying to assign a new value to a formal parameter will not change the value of the actual parameter after the macro terminates execution, but changing the internal data of a passed object will work. ''This is the behavior of Java method calling.'' '''Warning: the call-by-sharing behavior is new in Velocity 1.7. The previous behavior was closer to true call-by-macro expansion.'''
* '''Relaxed''', or '''error-free non-lvalue assignment:''' trying to assign a value to a formal parameter which is bound to a non-lvalue, for example calling `#someMacro('a')` where `someMacro` tries to assign a new value to that parameter, will create a new variable which shadows the formal parameter. ''This is closer to the behavior of C method calling, but holds true only when assigning values to non-lvalue arguments.''
== Examples ==
=== Call by sharing example (new behavior introduced in 1.7b1, and shortly only in 1.6.1): ===
{{{
#macro(callBySharing $x $map)
#set($x = 'a')
$map.put('x', 'a')
#end
#set($y = 'y')
#set($map = {})
#callBySharing($y $map)
$y -> 'y' (but is 'a' in 1.6.2, 1.6.0 and before)
$map.x -> 'a'
}}}
Java-like behavior. See https://issues.apache.org/jira/browse/VELOCITY-681
=== Call by name/macro expansion example (and call by need counter-example) ===
{{{
#macro(callByMacro1 $p)
not using
#end
#macro(callByMacro2 $p)
using: $p
using again: $p
#end
#set($x = [])
#callByMacro1($x.add('t'))
$x -> [], the add call was not executed
#callByMacro2($x.add('t'))
$x -> [t,t], the add call was executed twice
}}}
This is a classic call by name example.
=== Call by value(?) example (and call by name or expansion counter-example) ===
{{{
#macro(callByValueSwap $a $b)
$a $b becomes ##
#set($tmp = $a)
#set($a = $b)
#set($b = $tmp)
$a $b
#end
#callByValueSwap('a', 'b') ->
a b becomes b a
}}}
In a true call-by-name (or macro-expansion) implementation, `$a` would always be `'a'`. What actually happens is that `#set($a = $b)` creates the global variable $a which shadows the formal parameter. This is a bit strange, since formal parameters should never be shadowed by external parameters. Since `$a` is not an lvalue, a call-by-name behavior would result in an exception.
=== Call by macro expansion example (and call by value or sharing counter-example) ===
{{{
#macro(changeMap $map)
Before: $map.someKey
#set($map.someKey = 'new value')
After: $map.someKey
#end
#changeMap({'someKey' : 'old value'}) -> Before: old value, After: old value
}}}
If this was true call-by-sharing (or call-by-reference), then $map would be a pointer to a real map which would be changed by the first set. See https://issues.apache.org/jira/browse/VELOCITY-684
=== Call by macro expansion example (exposes name capture, call by name counter-example) ===
{{{
#macro(nameCaptureSwap $a $b)
$a $b becomes ##
#set($tmp = $a)
#set($a = $b)
#set($b = $tmp)
$a $b
#end
#set($x = 'a')
#set($tmp = 'b')
#nameCaptureSwap($x $tmp) ->
a b becomes a a
}}}
This is the classic name capture example, which only happens with call by macro expansion.
Mixing different types of actual and formal parameters will expose mixed behaviors.
== Future ==
The current (1.7 beta 1) behavior is complex, non-uniform and surprising in several (not so common) cases, and might be revisited for 2.0. The most likely candidate is a true ''call by sharing'' behavior, familiar to Java developers.