javakotlincollectionsjava-streamside-effects

Is there any recommendations for side effects in Kotlin foreach?


In Java Streams is recommended to avoid to use shared state in foreach.
Proof: java streams doc

Side-effects Side-effects in behavioral parameters to stream operations are, in general, discouraged, as they can often lead to unwitting violations of the statelessness requirement, as well as other thread-safety hazards. If the behavioral parameters do have side-effects, unless explicitly stated, there are no guarantees as to the visibility of those side-effects to other threads, nor are there any guarantees that different operations on the "same" element within the same stream pipeline are executed in the same thread. Further, the ordering of those effects may be surprising. Even when a pipeline is constrained to produce a result that is consistent with the encounter order of the stream source (for example, IntStream.range(0,5).parallel().map(x -> x*2).toArray() must produce [0, 2, 4, 6, 8]), no guarantees are made as to the order in which the mapper function is applied to individual elements, or in what thread any behavioral parameter is executed for a given element.

Many computations where one might be tempted to use side effects can be more safely and efficiently expressed without side-effects, such as using reduction instead of mutable accumulators. However, side-effects such as using println() for debugging purposes are usually harmless. A small number of stream operations, such as forEach() and peek(), can operate only via side-effects; these should be used with care.

As an example of how to transform a stream pipeline that inappropriately uses side-effects to one that does not, the following code searches a stream of strings for those matching a given regular expression, and puts the matches in a list.

 ArrayList<String> results = new ArrayList<>();
 stream.filter(s -> pattern.matcher(s).matches())
       .forEach(s -> results.add(s));  // Unnecessary use of side-effects!

This code unnecessarily uses side-effects. If executed in parallel, the non-thread-safety of ArrayList would cause incorrect results, and adding needed synchronization would cause contention, undermining the benefit of parallelism. Furthermore, using side-effects here is completely unnecessary; the forEach() can simply be replaced with a reduction operation that is safer, more efficient, and more amenable to parallelization:

 List<String>results =
     stream.filter(s -> pattern.matcher(s).matches())
           .collect(Collectors.toList());  // No side-effects!

I can't find the same recommendsations for Kotlin.

Should I avoid such code in Kotlin ?

var bar = foo(myPath)
myPath.forEach { e ->
    ...         
    bar = foo(bar,....)
}

Solution

  • Unlike Java's streams which can be run in parallel, the functions in the kotlin.collections package (forEach is one of them) never run concurrently. Therefore, the thread safety issues mentioned in the quoted javadocs are not relevant.

    forEach (among many other functions in kotlin.collections) is an inline function. This means that its function body is directly inserted into the call site during compilation. The lambda you passed to it is also inlined.

    By inspecting the source, we can see that calling forEach is exactly the same as a for loop.

    list.forEach { f(it) }
    

    is inlined to

    for (it in list) {
        f(it)
    }
    

    So in the compiled binary, there is no longer a call to forEach, and there is no longer a lamnbda. The code behaves exactly the same as if you wrote the for loop instead of calling forEach.