If you want to write a DSL where the user can call...
# announcements
n
If you want to write a DSL where the user can call some function to "break out" early from their own function, does this have to be implemented using exceptions internally? Is this considered bad? Here's a toy attempt at emulating python's else clause on for loops:
Copy code
class BreakException : Exception("")

class Breaker {
    fun breakOut() { throw BreakException(); }
}

class ForEachElser(val runElse: Boolean)

fun <T> Iterable<T>.forEachElse(func: Breaker.(t: T) -> Unit): ForEachElser {
    val b = Breaker()
    return try {
        forEach{ b.func(it) }
        ForEachElser(true)
    }
    catch (b: BreakException) {
        ForEachElser(false)
    }
}

infix fun ForEachElser.elser(func: () -> Unit) { if(runElse) func() }
đź‘€ 1
@holgerbrandl you might enjoy this, usage looks like this:
Copy code
(1..5).forEachElse {
        if (it == 6) {
            println("x is 5")
            breakOut();
        }
        
    } elser {
        println("hello")
    }
👍 2
c
An exception would be the only way to stop the loop in the middle. An alternative would be to use some “cancelled” flag to prevent the next iteration but still finish the current iteration. But throwing an exception isn’t a bad way to implement that behavior. It’s basically the same way coroutines handle failure/cancellation
n
Fair enough, I didn't know if people would be like, eww, you used exceptions internally to implement that
perhaps really bad performance wise
h
Thanks Nir for this great dsl solution.
n
np. It's just a pity we're not allowed to somehow use names like break and else here, ther'e sprobably better names than breakOut and elser
It does solve the double nesting
the other weird thing is that elser cannot start on its own line
h
I still prefer your run suggestion because it's more readible for a wider audience.
n
yeah probably true
m
np. It’s just a pity we’re not allowed to somehow use names like break and else here,
actually you can but you need to enclose them in backticks: `infix fun ForEachElser.`else`(...)` they’re needed for invocation as well
n
yeah, but like you said, you'd need them to invoke
which kind of kills the fun
m
yeah I know, sad tradeoff
n
yeah. I can respect why they did it, of course, it's just a bit unfortunate since kotlin lets you write these beautiful DSL's, without even using macros
m
Well yes, they had to make the compiler work somehow while allowing you to use any name. In my experience backtick identifiers are really useful when writing descriptive test names like
Copy code
fun `should add two numbers`() { ...
Kotlin DSLs are one of the reasons I started loving this language, but apart from this naming nuisance they let you write very neat code!
h
So true, test names rock with kotlin.
K 1
y
You actually can do this using the suspension system but it's a bit complicated. I'd say take a look at what the Arrow team has been doing with implementing delimited continuations to exit early out of functions, but basically you'll need to implement Arrow's
Effect
interface for your own type and have it so that whenever breakOut is called you'll
control().reset(//some value)
out of it. Maybe check out the #C5UPMM0A0 channel for more detailed information and demos
Here's a recent demo that they made implementing a database connection that can fail but inside the block you're just allowed to pretend like it never fails and it handles exiting automatically: https://gist.github.com/raulraja/ac7a489c01193bcf5e8fdc59c89b5156
n
thanks for the information, I'll check out that code snippet carefully!