CLOVIS
03/14/2024, 4:48 PMfoo
only accepts one configuration):
foo {
option1(true)
option1(false) // illegal, 'foo' has already been configured!
}
but this is correct (and
combines multiple configurations in a single one):
foo {
and {
option1()
option2()
}
}
the difference is important because there is multiple ways to combine configuration (or
…). Otherwise, I would have just made and
the default.
Is there a way to make the first example not compile? Even better if the error message can point users to and
and or
.Adam S
03/14/2024, 5:31 PMAdam S
03/14/2024, 5:36 PMand {}
as an infix function?
foo { option1() and option2() }
Adam S
03/14/2024, 5:36 PMJavier
03/14/2024, 5:38 PMJeff Lockhart
03/14/2024, 5:38 PMfoo
accept the configuration directly instead of a lamda. That way it's limited to only one. It changes the DSL of course though.
foo(
option1(true)
)
or
foo(
and {
option1()
option2()
}
)
CLOVIS
03/14/2024, 5:40 PMWould it make more sense to haveNo, there could be many, many more than 2 options.as an infix function?and {}
CLOVIS
03/14/2024, 5:43 PMMaybe consider makingI thought of this, but there are a few problems: • the function cannot introduce a receiver anymore, so I lose type safety for the options • this forces introducing a variant ofaccept the configuration directly instead of a lamda. That way it's limited to only one. It changes the DSL of course though.foo
option1()
that returns a value, whereas the DSL-variant mutates the DSL itself. Because that new variant will be top-level without a context, it will become extremely easy to call it when meaning to call the receiver variant, which would no-op, so your configuration would be swallowed into the void if you import the wrong variant 😕Adam S
03/14/2024, 5:51 PMCLOVIS
03/14/2024, 5:52 PMAdam S
03/14/2024, 5:58 PMval options = object : FooOptions {
override val option1 by true
}
val combiner = object: FooCombiner<FooOptions> {
override fun combine(opts: FooOptions): FooCombined = ...
}
doTheThing(options, combiner)
CLOVIS
03/14/2024, 5:58 PMmolikuner
03/18/2024, 10:24 PMfun main() {
foo {
option1(true)
option1(false)
}
}
@DslMarker
annotation class MyDsl
@MyDsl
class FooBuilder {
internal class ConfigurationFinished : RuntimeException()
}
// use FooBuilder.() -> Unit if setting an option is optional
fun foo(block: FooBuilder.() -> Nothing): Unit {
val builder = FooBuilder()
try {
builder.block()
} catch (ignored: FooBuilder.ConfigurationFinished) {
// actually we expect this exception
}
}
fun FooBuilder.option1(something: Boolean): Nothing {
println("configuring $something")
// configure something
throw FooBuilder.ConfigurationFinished()
}
This will at least create a warning and with "warning as error" compiler option you could make the build fail.