https://kotlinlang.org logo
#getting-started
Title
# getting-started
e

elect

10/02/2021, 10:40 AM
I'd like to create a variable by delegation
Copy code
val username by Input { }
Where
Copy code
class Input {
      companion object {
         operator fun invoke(..): Input {
            ..
         }
      }
   }
And
Copy code
internal operator fun Any.getValue(thisRef: Any?, property: KProperty<*>): Input {
    println("$thisRef, thank you for delegating '${property.name}' to me!")
    val pInput = property as KProperty0<Input>
    println(pInput)
    return pInput.get()
}
pInput
is a valid
KProperty0
class, but the moment I call
.get()
to retrieve
Input
, then I get:
java.lang.UnsupportedOperationException: call/callBy are not supported for this declaration.
Why? How can I fix it? What is the way to retrieve the
Input
instance produced by its own companion object?
j

Joffrey

10/02/2021, 10:51 AM
Why declare
invoke
on the companion instead of just using a constructor?
Also, where is
getValue
declared here? Inside the
Input
class or at the top level?
e

elect

10/02/2021, 10:53 AM
top level
I can also switch to a constructor, but I'm building a dsl, so that's kind of the builder which then later on will produce the resulting
Input
j

Joffrey

10/02/2021, 10:55 AM
I don't understand what you're trying to do here. Why would you define such an operator on
Any
?
e

elect

10/02/2021, 10:56 AM
because in this way I can automatically retrieve the variable name (
username
) as well and use it to create
Input
(it's one of the fields)
j

Joffrey

10/02/2021, 10:58 AM
You are delegating
username
so the delegate is the one that's supposed to provide the value
Regarding invoke on the companion, I still don't get why. The fact that you're using this for a DSL is unrelated to where you define the function
e

elect

10/02/2021, 11:00 AM
how would you do it?
Copy code
class Input {
   fun getValue(..)
}
?
btw, the delegate (companion object
invoke
) is actually providing the value, or am I wrong?
I'm also delegating on
Any
so that I can also have type inference
j

Joffrey

10/02/2021, 11:51 AM
You seem to be confusing the delegate object and the value of the property. It looks like you want
Input
to be the type of the delegate but you also want it to be the type of the value? I cannot really suggest anything at the moment because I didn't get what you're trying to do. What is the purpose of the delegate?
e

elect

10/02/2021, 11:54 AM
I'd like to initialize
username: Input
in the lambda, this should include saving the variable name itself, that is
username
inside the
Input
instance itself
sort of Gradle does when you
val task by tasks.creating { }
j

Joffrey

10/02/2021, 12:06 PM
Then I don't believe you need a delegate at all, you can simply use
val username = Input { ... }
e

elect

10/02/2021, 12:07 PM
I cant retrieve
username
from inside
Input{..}
in that way
username
is
Input.id
I think I got it
Copy code
class Delegate(val block: Input.() -> Unit) {

    operator fun getValue(thisRef: Any?, property: KProperty<*>): Input {
        println("$thisRef, thank you for delegating '${property.name}' to me!")
        return Input().apply {
            block()
            id = property.name
        }
    }
}
j

Joffrey

10/02/2021, 12:15 PM
Ah ok got it now, then indeed you can use a delegate for this, but in this case you need to separate the delegate type from the
Input
type. The code you provided last can work, but then you'll create a new instance of
Input
every time you access
username
. You can use the
provideDelegate
approach in order to store the value and return the same one everytime it's accessed
e

elect

10/02/2021, 12:17 PM
sorry for the confusion, I didn't get it right the difference between delegate class and return value class
what's the
provideDelegate
approach?
j

Joffrey

10/02/2021, 12:20 PM
Yes, that's it. It's one more level of indirection where a
provideDelegate
function is called once when the class containing the delegated property is initialized. This function creates an instance of the delegate class and you already have access to the property name when doing so, so you can store the value in the delegate instance itself
Here is how I think you could use it: https://pl.kotl.in/MuYRhTYEs
Untitled.cpp
e

elect

10/02/2021, 4:44 PM
cool, thanks a lot
I still don't get why a plan
by
won't save the value in the variable by default, like
by lazy
, instead of requiring all this boilerplate code, but anyway
I see that
One of the possible use cases of
provideDelegate
is to check the consistency of the property upon its initialization.
I double checked though, the class gets initialized just once
ps: is there a way I can use the
Copy code
PropertyDelegateProvider
(at the end of the same doc link posted above) I guess no, because there isn't a way to pass the lambda
j

Joffrey

10/02/2021, 6:23 PM
Hey sorry for the delay, I was AFK. I actually didn't know about
PropertyDelegateProvider
and never thought of using SAM conversions like this! It is definitely possible to use by capturing the
configure
lambda (and it would simplify the whole thing quite a bit):
Copy code
private fun Input(configure: Input.() -> Unit) = PropertyDelegateProvider { thisRef: Any?, prop ->
    val input = Input(prop.name).apply { configure() }
    ReadOnlyProperty<Any?, Input> { _, _ -> input }
}
So the whole thing is now: https://pl.kotl.in/n6KFhfDOp Thanks for making me learn this 🙂
I still don't get why a plan 
by
 won't save the value in the variable by default, like 
by lazy
I don't get your point here. The implementation of the lazy delegate does need to store the
value
explicitly, look: https://github.com/JetBrains/kotlin/blob/master/libraries/stdlib/src/kotlin/util/Lazy.kt#L95 However, they don't need the additional indirection of
provideDelegate
because they don't need information about the property itself (like the name of the property), unlike in your use case.
e

elect

10/02/2021, 6:47 PM
thank you for helping me out, appreciated