Hello, I’m working on a command framework in Kotli...
# getting-started
a
Hello, I’m working on a command framework in Kotlin, and I have a specific use case where I want to retrieve the value of an argument within the
execute
function of a command. Below is a simplified version of my current setup:
Copy code
abstract class Command {
    abstract suspend fun ArgumentData.execute()
}

class Argument<T> {
    lateinit var name: String

    operator fun <T> getValue(thisRef: Any?, property: KProperty<*>) = this
}

// I've also tried to define `getValue` as an extension function but that does not help either
// operator fun <T> Argument<T>.getValue(thisRef: Any?, property: KProperty<*>): Argument<T> {
//     return this
// }

class ArgumentData(map: Map<String, Any?>) {
    inline operator fun <reified T> Argument<T>.getValue(thisRef: Any?, property: KProperty<*>): T {
        return get<T>(name) // Retrieve the value from the map
    }
}
Here's how it is supposed to be used:
Copy code
command("example") { // this = ArgumentData
    val exampleArgument: Argument<String> by argument()

    execute {
        println(exampleArgument)
    }
}
The question is, how can I modify this code so that
println(exampleArgument)
outputs the value of the argument rather than the
Argument
class itself, in other words, use the extension function provided by
ArgumentData
instead? Is this behavior possible to achieve at all? Thanks in advance!
I also tried to use coroutine contexts but since suspend getValue is yet to be implemented, I cannot access
coroutineContext
from a non-suspending function. I could not find a way to store the context within the command either, as the command is instantiated only once and subsequent calls would simply overwrite the context of previous executions. Any suggestions?
c
First, I recommend making
Command
an interface. >
Copy code
operator fun <T> getValue(thisRef: Any?, property: KProperty<*>) = this
The return value of this function is what gets printed. Currently, you put
= this
, so it always prints the
Argument
itself. If you put
= name
, it will print its name. You could put anything else. Since this will change the type of the delegate value, your DSL will change a bit:
Copy code
command("example") {
    val exampleArgument: String by argument()

    execute { … } 
}
note how
exampleArgument
is declared as
String
(or whatever else you want returned when the variable is read) and not
Argument<String>
.
a
I explicitly set the type to be String but the following error appeared
I suppose it's not possible to change how a property is delegated based on the receiver. What I'm really aiming for is to access
ArgumentData
from an
Argument
, where
ArgumentData
is unique to each command invocation, and
Argument
defined is per command. If only
getValue
were a suspending function, I could store a global coroutine context and retrieve the arguments based on the current invocation. These are the only two approaches I can think of to address this. Is this simply a limitation of the language atp?
y
You need to remove the
getValue
from Argument completely. Instead, use the extension from
ArgumentsData
.
a
@Youssef Shoaib [MOD]
ArgumentsData
is only available inside of
execute
, so I'd have to define the arguments there instead. But that way arguments aren't identified until first execution, and that has to happen before.
c
I see, you're trying to share data implicitly that's not available at variable creation time. IMO the best way there is to add an
invoke()
operator on
Argument
, that's only available in the
execute
lambda. You can see an example of this here: the
prepared
builder (similar to your
argument
) is available anywhere, but its value can only be accessed within
test
(your
execute
) by using the invoke operator.
a
Yes, I've already set up several ways to access the value within
execute
, but I was hoping to find a way to delegate it directly. It seems that's not possible at the moment. Thanks for your help.
a
I’ve had the exact same situation before with argument parsing, and have always found it somewhat strange that the getValue method is decided when the property is declared rather than when it is accessed (and actually calls the getValue method) although i understand it would likely be complicated to implement something like that into the language properly
c
It's because it has to be a regular getter, so you can't access any more information from the caller. If you could (e.g. with context properties, which are part of the context parameters KEEP), then you would be able to do what you want here.