14.1. Principles and syntax

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.

Note

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.

Warning

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:

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.