is there a Kotlin counterpart of <keyword-only arg...
# getting-started
p
is there a Kotlin counterpart of keyword-only args in Python? I want to be able to force the call site of a function to use the named parameters. I found https://stackoverflow.com/questions/37394266/how-can-i-force-calls-to-some-constructors-functions-to-use-named-arguments but it looks shady - produces some warnings, I’m wondering if we could get something closer to the intent
g
No, there is no such feature, only feature request on official issue tracker
p
👌 1
g
Be free to add your use case, always helpful
👌 1
c
Why do you want this?
p
in the context of https://github.com/krzema12/github-workflows-kt/, I generate a Kotlin class for each GitHub action. The users occasionally have issues when using positional arguments because the GitHub Actions API doesn’t rely on positions but on dictionary of arguments instead. I’d like to reflect it on the Kotlin layer as well
also see the warning in one of the recent releases: https://github.com/krzema12/github-workflows-kt/releases/tag/v0.37.0 “Warning: the below changes may break your workflow code. …”
g
I feel that this feature is somewhat related (not sure that it’s blocked per se, just related) to named destructuring which is also proposed, maybe I’m wrong though
p
As prior art, there is the
CopyWithoutNamedArguments
compilation warning on
.copy(...)
usage without named arguments - it would be nice to have a
@RequireNamedArguments
annotation you can add where required to provide a similar warning (which could be done using #detekt rule for now)
👍 2
a
the only strict way I can think of is to make every argument a value class, which is verbose, annoying to use, and complicated to set up. As a soft measure, you could create a custom IntelliJ inspection. But to share this with everyone requires an IntelliJ plugin...
p
the only strict way I can think of is to make every argument a value class, which is verbose, annoying to use, and complicated to set up.
I’d prefer the good old Java builder over this 🙂 but I probably won’t implement it
But to share this with everyone requires an IntelliJ plugin...
yeah, exactly - the same with linter rules
a
for this specific functionality an IJ plugin would be too much work, but if you want other functionality as well, like having a custom script context instead of using
.main.kts
, then an IJ plugin is more tempting...
🤔 1
p
following up on this particular idea ☝️ here, but this very thread still needs to be decoupled from the scripting - a generic solution would be nice
a
maybe using a lambda builder might be more explicit? At least it requires property names. But it's possible to forget to add parameters.
Copy code
import kotlin.properties.Delegates.notNull

class Parameters {
  var foo: String by notNull()
  var bar: Int by notNull()
  
  override fun toString() = "Parameters(foo=$foo,bar=$bar)"
}

fun someFunction(init: Parameters.() -> Unit) {
  val params = Parameters()
  params.init()
  println(params)
}

fun main() {
  someFunction {
    foo = "abc"
    bar = 123
  }
}
Playground link
p
But it’s possible to forget to add parameters.
exactly - we’d sacrifice one kind of safety measure for the other, I’d rather stay with the current approach, If we had a mechanism to enforce using all intended parameters in a lambda, then it’s a sensible thing to consider
p
It's not pretty, but achieves what you want:
Copy code
sealed interface Marker

fun forcedNamedArguments(vararg _ignore: Marker,
                        name: String,
                        age: Int,
                        nickname: String = name) {
	println("$name ($nickname) is $age years old")
}

fun main() {
    forcedNamedArguments(
        name = "Robert", 
        age = 32, 
        nickname = "Bob"
    )
}
p
yeah, pretty much what https://stackoverflow.com/a/37394267 suggests - but thanks anyway! I’m considering using it, especially in the generated code
p
I think I prefer using the
sealed interface
approach as there’s no need for the suppressions, and the only downside is someone could call it with
myFun(*emptyArrayOf<Marker>(), "Robert", 32)
👍 1
p
the only downside is someone could call it with (…)
good point, however it would have to be a conscious decision, not an accident