A function call marked with bang (!) will be called only once,
the result is stored as a constant and will be directly returned for every subsequent call.
A function call can be marked with a bang like in the following example:
module sample
function take_a_while = {
# ... complex computation
return 42
}
function main = |args| {
foreach i in range(0, 100) {
take_a_while!()
}
}In this example take_a_while is computed only once at the first call, and then this function returns directly the previously computed result as a constant for every subsequent call.
The ! notation can only be used on regular function calls. Indeed,
since methods are context dependant (the object itself), it is not allowed to
“bang” them. As a consequence, a function invocation using the invoke or
invokeWithArguments method of the MethodHandle object can’t use this
feature.
Bang function call is a kind of memoization but regardless of the given parameters:
module sample
function hello = |name| {
return "Hello " + name + "!"
}
function main = |args| {
foreach name in ["Peter", "John", "James"] {
println( hello!(name) # will always print 'Hello Peter!'
}
}In this example hello is executed at the first call with the parameter
"Peter", then always returns "Hello Peter!", even when called with other
values.
Functions having side effects should not be marked, since the computation
is not done for subsequent calls, and thus the side effect can’t happen. In the
same way, function that depends on an outside context are risky. Indeed, a
change in the context won’t imply a change in the result any more. In other
words, only pure functions should be marked with a !. No check is done by
the language, use it at your own risk.
The result of a banged function call is constant within the same call place, but different for each call instructions.
module sample
function hello = |name| {
return "Hello " + name + "!"
}
function main = |args| {
println( hello!("Foo") ) # will print 'Hello Foo!'
println( hello!("Bar") ) # will print 'Hello Bar!'
foreach name in ["Peter", "John", "James"] {
println( hello!(name) # will always print 'Hello Peter!'
}
foreach name in ["Peter", "John", "James"] {
println( hello(name) # will print 'Hello Peter!', 'Hello John!', 'Hello James!'
}
}In the previous listing, the hello!(name) in the loop is considered the same
call, and thus evaluated only on the first iteration. On the other hand, the
previous calls with "Foo" and "Bar" are distinct, and therefore prints
different results.
Anonymous function call and object constructor call can be banged too:
module sample
function closure = |x| {
return |y| {
return x * y
}
}
function singleton = -> java.lang.Object!()
function main = |args| {
foreach i in range(0, 100) {
println( closure(i)!(i) ) # will always print 0
}
require(
singleton(): hashCode() == singleton(): hashCode(),
"Houston, ..."
)
}In this example closure(i)!(i) always return 0 because:
closure(i) returns a closure (|y| -> x * y) with x as enclosed variable
closure(i) is computed for each value of i
closure(i) is called at the first iteration with 0 for x and y
closure(i) is still computed but ignored because the anonymous call is replaced by the return of a constant value
The singleton function return a new java Object but the java.lang.Object is created with a banged constructor call, then the returned reference is constant.