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]

Reply via email to