Unit return value useful to break out of nested lambdas


#1

I wrote some simple tree visitor functions but found it a pain to reuse the same functions when searching for a value because there is no way to break out of nested loops using lambda. Then decided to use any return value other than Unit to signal a loop break.

This propagates up through nested calls and makes for a very flexible way to break out early and return any value, including null. With Unit signalling no early termination.

Here is a very basic class for demonstration:

import java.util.*

interface Node {
    val value: Any
    val children: Iterable<Node>
    val parent: Node

    fun unlink() 
    
    fun accept(visitor: (Node) -> Any?): Any? {
        var result = visitor(this);
        if (result != Unit) return result

        for (child in children) {
            result = child.accept(visitor)
            if (result != Unit) return result
        }
        return Unit
    }

    fun visitOf(value: Any?, visitor: (Node) -> Any? = { it }): Any? {
        return accept {
            if (it.value == value) visitor(it) 
            else Unit
        }
    }

    fun removeAllOf(value: Any?): Any? {
        return visitOf(value) { 
            it.unlink()
            Unit
        }
    }

    fun removeFirstOf(value: Any?): Any? {
        return visitOf(value) { 
            it.unlink()
            it
        }
    }

    fun findFirstOf(value: Any?): Any? {
        return visitOf(value) {
            it
        }
    }
    
    fun listAllOf(value: Any?): List<Node> {
        val list = ArrayList<Node>()
        visitOf(value) {
            list.add(it)
            Unit
        }
        return list
    }
}

Very compact and the only thing to look out for is implicit return that will abort and return that value.

forEach is limited because it cannot break out early. This is too convenient not to use. I will add a forAll to my iterables that will treat a non-Unit return from the lambda as an early loop break.


#2

For searching the first occurrence you can use
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/first.html
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/first-or-null.html


#3

The search first was just an example of breaking out from a lambda as a demonstration. Real usefulness comes when you need to implement more complex conditions.


#4

As in most cases you will want to have an iterator, it is quite easy to use the first or firstOrNull functions on top of that iterator (or on top of a filter as intermediary).

What you are doing is effectively an interruptable visitor that also returns a possibly null value. In that specific case (if an iterator is going to be problematic) conceptually you need a union type (or tuple) to indicate the “no value” result that is distinct from null (java references are effectively union types as well). As the no value type can be represented through a single (sentinel) value, the use of Unit in this case is quite ok (at the cost of type-safety).


#5

@pdvrieze, my point is that Unit can be more useful than just an implicit return type and value.

Iterators take more than 7 lines of code to implement and lugging a tuple around instead of checking for not Unit has more overhead on all fronts.

Type safety is great when it works for your use case. When it does not, it gets in the way with a lot of unnecessary complexity, boiler plate code and overhead.


#6

@vladimir_schneider I agree with you in principle. In many cases you have an iterator anyway. But if you don’t your idea has merit. Tuples certainly are overkill here.