Loops
ISL provides the support for loops which help in executing a certain sequence of instructions in a continuous and repeated manner unless a certain condition is reached. They could also be used to iterate over a collection of elements.
foreach loop
The foreach loop can be used for traversing items in a collection. A foreach loop can also be used to generate child
properties.
$array: [ 1, 2, 3 ];
$result: {
lines: foreach $i in $array
{
id: $i,
total: {{ $i * 10 }} // math expression
}
endfor
}
Will output:
{
"lines": [
{
"id": 1,
"total": 10
},
{
"id": 2,
"total": 20
},
{
"id": 3,
"total": 30
}
]
}
An example of using the foreach loop to transform each payload item of a page into an order.
// assume list of orders is in the $page.orders
// captures $orders as [ { order } ]
$orders: foreach $o in $page.orders
# convert each payload into an order
@.This.Transform( $o )
endfor
To access the current item index, a variable called $<name>Index is automatically created within the foreach loop, where <name> is the variable name of the iterator
$orders: foreach $o in $array
# The index in this instance is $oIndex
$currentIndex : $oIndex;
endfor
Filtering in For Loops
Filtering in foreach loops can easily be achieved using the | filter( condition ) modifier which is more convenient and more efficient
than an individual if statement:
$orders: foreach $o in $page.orders | filter ( $.total > 0 )
# convert each payload into an order
@.This.Transform( $o )
endfor
For Loop Readability
NOTE: Generally try to avoid the foreach/push pattern
$lines = []
foreach $item in $invoice.lines
$transformed = @.This.Transform ( $item )
$lines = $lines | push( $transformed )
endfor
This pattern can be replaced with the cleaner:
$lines = foreach $item in $invoice.lines
@.This.Transform ( $item )
endfor
Or if you need filtering:
$lines = foreach $item in $invoice.lines | filter ( $.total > 0 )
@.This.Transform ( $item )
endfor
Parallel foreach loops
Available since: ISL 2.4.17
foreach loops over any array can be parallelized in processing (if the underlying host supports it).
NOTE: Parallel processing is only supported in Kotlin runtime that’s using Kotlin coroutines.
Simple usage:
$result = parallel foreach $o in $page.orders | filter ( $.total > 0 )
# convert each payload into an order
$r = @.This.Transform( $o )
$r // return can capture the $r result
endfor
The loop above will be executed in parallel on up to 6 different workers. The resulting $r at the end will be used to capture one result
and add it to the $result array. The order of the original input is respected and the $result will contain the results in the same order, even if the actual processing was done in parallel.
Parallel options can be used to control the number of maximum parallel workers:
$result = parallel { workers: 20 } foreach $o in $page.orders | filter ( $.total > 0 )
# convert each payload into an order
$r = @.This.Transform( $o )
$r // return can capture the $r result
endfor
Enabling Parallel Processing
By default parallel processing is disabled as it’s dependent on the host.
Only Kotlin hosts that run code in coroutines can parallelize foreach statements.
In order to enable parallel processing in ISL, you need to set a value in Transformer.maxParallelWorkers:
Transformer.maxParallelWorkers = 10
This enables maximum of 10 workers for ISL parallel processing.
The value can be read and logged from ISL code:
@.Log.Info("Maximum parallel workers={}", $isl.maxParallelWorkers )
By default the value is 1 which disables parallel processing.
Thread safety
Modifying variables that are outside of the scope of the parallel foreach is not allowed. You can read, but you can’t write.
CRITICAL: An attempt to write to a variable outside of the scope of the foreach will generate an exception and terminate the execution of the script.
For example this script will fail:
$result = []
parallel { workers: 20 } foreach $o in $page.orders | filter ( $.total > 0 )
# convert each payload into an order
$r = @.This.Transform( $o )
$result = $result | push ( $r ) // this will fail and terminate the script
endfor
To capture the result you need to use one of the following two approaches:
// Option 1 - return a variable from inside the parallel foreach
$result = parallel { workers: 20 } foreach $o in $page.orders | filter ( $.total > 0 )
# convert each payload into an order
$r = @.This.Transform( $o )
// return a variable - these will be captured in $result[]
$r
endfor
// Option 2 - return a new object
$result = parallel { workers: 20 } foreach $o in $page.orders | filter ( $.total > 0 )
# convert each payload into an order
// create and return an object - these will be captured in $result[]
{
id: $o.id
}
endfor
while loop
The while loop allows the code to be executed repeatedly based on a given boolean condition. In ISL, the while loop
has an option to pass a parameter maxLoops which defines the maximum number of iterations of the loop. By default, the
maxLoops is set to 50 and can be increased up to 5000. This has been done to avoid any programmer from running into an
infinite loop.
$i: 0
while ( $i < 5 )
$i: {{ $i + 1 }}
endwhile
result: $i
Will output:
{ "result": 5 }
Another example (storing the result of a while loop):
$i: 1;
result: while ( $i < 4 )
{
first: $i,
$i: {{ $i + 1 }}
}
endwhile
Will output
result:
[
{ "first": 1 },
{ "first": 2 },
{ "first": 3 }
]
Using maxLoops option will restrict the number of iterations to the given value:
$i: 0
while ( $i < 150 , { maxLoops: 100 })
$i: {{ $i + 1 }}
endwhile
result: $i
Will output:
{ "result": 100 }