https://kotlinlang.org logo
n

Nir

01/15/2021, 4:01 PM
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

Casey Brooks

01/15/2021, 4:11 PM
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

Nir

01/15/2021, 4:26 PM
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

holgerbrandl

01/15/2021, 4:28 PM
Thanks Nir for this great dsl solution.
n

Nir

01/15/2021, 4:30 PM
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

holgerbrandl

01/15/2021, 4:31 PM
I still prefer your run suggestion because it's more readible for a wider audience.
n

Nir

01/15/2021, 4:31 PM
yeah probably true
m

Matteo Mirk

01/15/2021, 4:43 PM
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

Nir

01/15/2021, 4:50 PM
yeah, but like you said, you'd need them to invoke
which kind of kills the fun
m

Matteo Mirk

01/15/2021, 4:50 PM
yeah I know, sad tradeoff
n

Nir

01/15/2021, 4:51 PM
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

Matteo Mirk

01/15/2021, 4:55 PM
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

holgerbrandl

01/15/2021, 4:56 PM
So true, test names rock with kotlin.
K 1
y

Youssef Shoaib [MOD]

01/15/2021, 7:28 PM
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 #arrow 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

Nir

01/15/2021, 7:35 PM
thanks for the information, I'll check out that code snippet carefully!
2 Views