Note Velocity 2.0 now always uses the call by sharing (aka Java) calling convention.
From 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:
#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.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.#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.
#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
#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.
#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.
#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
#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.
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.