Hi devs,
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.
Call by sharing example (new 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.
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'}) -> old value, then again 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.
In conclusion, Velocity macros work mostly as call-by-macro expansion
internally, with call-by-sharing external behavior, but affected by
automatic assignment of global variables when local variables can't be
assigned (using a non-lvalue as an lvalue). I haven't tested strict
mode, so I don't know what evaluation strategy is used in that case.
So, the question would be, should Velocity choose and stick to one of
the classic evaluation strategies? Should the current behavior be kept
as-is? In the latter case, it should be well documented to avoid
surprises. Well, it won't actually avoid surprises, but at least there
will be a piece of documentation to point to.
--
Sergiu Dumitriu
http://purl.org/net/sergiu/
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]