How do I do a "break" or "continue" when in a functional loop within Kotlin?

32,402

Solution 1

There are other options other than what you are asking for that provide similar functionality. For example:

You can avoid processing some values using filter: (like a continue)

dataSet.filter { it % 2 == 0 }.forEach {
    // do work on even numbers
}

You can stop a functional loop by using takeWhile: (like a break)

dataSet.takeWhile { it < 10 }.forEach {
    // do work on numbers as long as they are < 10, otherwise stop
}

A more complex, although nonsensical example where you want to do some processing, skip some resulting values, and then stop at a set of different conditions, would be:

dataSet.asSequence()
       .takeWhile { it >=  0 }    // a -1 signals end of the dataset (break)
       .map { it + 1 }            // increment each number
       .filterNot { it % 5 == 0 } // skip (continue) numbers divisible by 5
       .map { it - 1 }            // decrement each number by 1
       .filter { it < 100 }       // skip (continue) if number is >= 100
       .drop(5)                   // ignore the first 5 numbers
       .take(10)                  // use the next 10 numbers and end
       .forEach {
           // do work on the final list
       }

A combination of these functions tends to eliminate the need for continue or break. And there are endless different options here and more than can be documented. To get an idea of what can be done, it is best if you learn all of the functions available in the Kotlin standard library for collections, lazy sequences, and iterable.

Sometimes there are cases where you have mutating state that still needs to break or continue and is hard to do in a functional model. You can make it work using more complex functions like fold and reduce combined with the filter and takeWhile functions but sometimes that is harder to grok. Therefore if you really want that exact behavior you can use return from lambda expression which mimics a continue or break depending on your usage.

Here is a an example mimicking continue:

(1..5).forEach  {
    if (it == 3) return@forEach  // mimic continue@forEach
    // ... do something more
}

And you can go more complicated and use labels when you having nesting or confusing situations:

(1..3).forEach outer@ { x ->
    (1..3).forEach inner@ { y ->
        if (x == 2 && y == 2) return@outer // mimic continue@outer
        if (x == 1 && y == 1) return@inner // mimic continue@inner
        // ... do something more
    }
}

If you want to do a break you need something outside the loop that you can return from, here we will use the run() function to help us:

run breaker@ {
    (1..20).forEach { x ->
        if (x == 5) return@breaker  // mimic break@forEach
        // ... do something more
    }
}

Instead of run() it could be let() or apply() or anything naturally you have surrounding the forEach that is a place you want to break from. But you will also skip the code within the same block following the forEach so be careful.

These are inlined functions so really they do not really add overhead.

Read the Kotlin reference docs for Returns and Jumps for all the special cases including for anonymous functions.


Here is a unit test proving this all works:

@Test fun testSo32540947() {
    val results = arrayListOf<Pair<Int,Int>>()
    (1..3).forEach outer@ { x ->
        (1..3).forEach inner@ { y ->
            if (x == 2 && y == 2) return@outer // continue @outer
            if (x == 1 && y == 1) return@inner // continue @inner
            results.add(Pair(x,y))
        }
    }

    assertEquals(listOf(Pair(1,2), Pair(1,3), Pair(2,1), Pair(3,1), Pair(3,2), Pair(3,3)), results)

    val results2 = arrayListOf<Int>()
    run breaker@ {
        (1..20).forEach { x ->
            if (x == 5) return@breaker
            results2.add(x)
        }
    }

    assertEquals(listOf(1,2,3,4), results2)
}

Solution 2

takeWhile stdlib function may be used instead of break.

For example,

val array = arrayOf(2, 8, 4, 5, 13, 12, 16)
array.takeWhile { it % 2 == 0 }.forEach { println(it) } // break on odd
array.takeWhile { it % 3 != 0 }.forEach { println(it) } // break on 3 * n
Share:
32,402
Jayson Minard
Author by

Jayson Minard

If my high-quality answers help you out, please consider buying me a cup of coffee. I am caffeine powered by: https://ko-fi.com/apatrida

Updated on July 05, 2022

Comments

  • Jayson Minard
    Jayson Minard almost 2 years

    In Kotlin, I cannot do a break or continue within a function loop and my lambda -- like I can from a normal for loop. For example, this does not work:

    (1..5).forEach {
        continue@forEach  // not allowed, nor break@forEach
    }
    

    There are old documentation that mentions this being available but it appears it was never implemented. What is the best way to get the same behavior when I want to continue or break from within the lambda?

    Note: this question is intentionally written and answered by the author (Self-Answered Questions), so that the idiomatic answers to commonly asked Kotlin topics are present in SO. Also to clarify some really old answers written for alphas of Kotlin that are not accurate for current-day Kotlin.

  • Martin Evans
    Martin Evans over 6 years
    A usage example would improve your answer.
  • Vadzim
    Vadzim over 6 years
    Beware that this would copy all satisfying elements into newly allocated intermediate collection.
  • Mark
    Mark over 5 years
    When dealing with a sequence instead of (or created from) an array, the aforementioned concern about intermediate collections is irrelevant (since sequences are lazy and don't build intermediate collections). IMHO, this is a better answer than the accepted one, you just need to use it on a sequence.
  • Mark
    Mark over 5 years
    Using breaks and returns with labels like this is a code smell (IMHO). See @user8320224's answer below for a much more elegant and idiomatic functional solution.
  • Jayson Minard
    Jayson Minard over 5 years
    @Mark I adjusted the answer to be a combination of the functional models for this, and the literal answer.
  • Jayson Minard
    Jayson Minard over 5 years
    @store88 extended the answer to provide more options.
  • Jayson Minard
    Jayson Minard over 5 years
    Sequences are not always faster, depends on list size and the actions taken. This has been proven repeatedly.
  • Jayson Minard
    Jayson Minard over 5 years
    This is a complete misuse of any function which is designed to just find if a condition exists in the sequence and return true/false if so, ending as early as possible. It is not designed to be used for processing, nor can be used in the middle of a functional chain, but rather only at the end.
  • Mark
    Mark over 5 years
    @JaysonMinard if your comment was addressed to me (seems like it is), note that i didn't say a sequence would be faster--just that using a sequence would nullify the previously stated concern about intermediate collections.
  • Jayson Minard
    Jayson Minard over 5 years
    it was just a generic thought to make sure that wasn't inferred as well.
  • Yava
    Yava over 4 years
    unfortunately filter gives me a new list so I cant use it if I need to mutate something and @breaker needs something to label outside of it so its situational
  • Crag
    Crag almost 4 years
    forEachIndexed?