I have a property delegate using a context receiver:
class LoggingPropertyDelegate<T, V, L : Log>(
private var value: V,
private val toLog: T.() -> L
) : ReadWriteProperty<T, V> {
override fun getValue(thisRef: T, property: KProperty<*>) = value
context(Logger)
override fun setValue(thisRef: T, property: KProperty<*>, value: V) {
this.value = value
log(toLog(thisRef))
}
}
But when I try to use it on a property:
var myValue: Int by LoggingPropertyDelegate(0, { InfoLog("Changed to $myValue") })
I get an error that there is no suitable set functions for the delegate. If I remove the context from the method everything works as expected.
Is it not possible to use context receivers on property delegates?
It is possible to use a property delegate that has context receivers. You just need to provide the context receiver in some way.
First, note that you should put context(Logger) on the delegate class type, not on setValue:
context(Logger)
class LoggingPropertyDelegate<T, V, L : Log>(
If myValue is an instance property of some class Foo, then you can do:
context(Logger)
class Foo {
var myValue: Int by LoggingPropertyDelegate(0) { ... }
}
Note that if Foo is a data class, there seems to be a compiler bug that causes the compiler to crash. Perhaps context receivers are lowered to extra compiler parameters (?)
And then when instantiating Foo, you will need to provide a Logger:
val foo = with(someLogger) { Foo() }
// now you can access foo.myValue
(or instantiate Foo in another function with a Logger context receiver, of course)
If myValue is a local variable, you can also directly use with to introduce the context receiver instance, in addition to adding a Logger context receiver to the enclosing function.
Related
In the following code, the call member of Animal cannot be resolved even though Cat is specified as context receiver and it has a member named call.
interface Animal { val call: String }
object Cat : Animal { override val call: String = "Meow" }
object Dog : Animal { override val call: String = "Woof" }
fun <T : Animal> acquireAnimal(animal: T, block: context(T) () -> Unit) {
block(animal)
}
fun main() {
acquireAnimal(Cat) {
call
}
}
When I type this inside the lambda, then the IDE seems to suggest that the type of this is Any?.
If I do the same with a function without a generic context receiver, then it seems to get the type right.
Is this a limitation that is by design or is this a bug?
The fact that you cannot access call was a bug, which was fixed in Kotlin 1.7.20.
A workaround for lower versions is:
sealed interface TypeWrapper<out A> {
object IMPL: TypeWrapper<Nothing>
}
fun <T: Animal> acquireAnimal(animal: T, block: context(T) (TypeWrapper<T>) -> Unit) {
block(animal, TypeWrapper.IMPL)
}
fun main() {
acquireAnimal(Cat) {
val x = call // works!
}
}
However, the fact that this doesn't work is intended. Context receivers do not change the meaning of this. Since you are in a global function, this does not mean anything, and the existence of a context receiver does not change that.
Normally, to access the context receiver itself, you need to do a qualified this by appending the generated label for the context receiver:
context(Foo)
fun foo() {
val x = this#Foo
}
However, your context receiver is a type parameter, so according to the rules here, I don't think a label is generated for the context receiver.
I am reading this page about Delegated properties in Kotlin doc site.
import kotlin.reflect.KProperty
class Example {
var p: String by Delegate() // 1
override fun toString() = "Example Class"
}
class Delegate() {
operator fun getValue(thisRef: Any?, prop: KProperty<*>): String { // 2
return "$thisRef, thank you for delegating '${prop.name}' to me!"
}
operator fun setValue(thisRef: Any?, prop: KProperty<*>, value: String) { // 2
println("$value has been assigned to ${prop.name} in $thisRef")
}
}
fun main() {
val e = Example()
println(e.p)
e.p = "NEW"
}
The output is:
Example Class, thank you for delegating 'p' to me!
NEW has been assigned to p in Example Class
And I can understand the result.
But my question is, what if I print the e.p again after setting its value as NEW:
fun main() {
val e = Example()
println(e.p)
e.p = "NEW"
println(e.p) // print it again after setting new value on it
}
I expect it to print NEW. But the actual result is, it kept the same as the 1st println: Example Class, thank you for delegating 'p' to me!.
Kotlin playground at here.
Seems like the e.p = "NEW" cannot change the value correctly. What causes that? What do I do if I want to set the value as NEW?
Your delegate class instance completely takes over what the getter and setter of the property do. Since your delegate’s setValue function doesn’t actually store the passed-in value in any internal property, it’s getValue function has no way to retrieve and return it. And indeed, the implementation of getValue() in your code is only generating a String and returning that.
When you get the value of the delegated property, it returns whatever your delegate returns in getValue(), so the behavior is determined by how you program your delegate class.
I am trying to create delegate var properties with a delegate that does not provide a setValue(...) method. In other words, I need a property that I can reassign but that should get its value via the delegate as long as it hasn't been reassigned.
I am using the xenomachina CLI arguments parser library, which uses delegates. This works well as long as I have val properties. In some cases I need to be able to change those properties dynamically at runtime, though, requiring a mutable var. I can't simply use a var here, as the library does not provide a setValue(...) method in its delegate responsible for the argument parsing.
Ideally, I'd like something like this:
class Foo(parser: ArgParser) {
var myParameter by parser.flagging(
"--my-param",
help = "helptext"
)
}
which doesn't work due to the missing setter.
So far, I've tried extending the Delegate class with a setter extension function, but internally it also uses a val, so I can't change that. I've tried wrapping the delegate into another delegate but when I do that then the library doesn't recognize the options I've wrapped anymore. Although I may have missed something there.
I can't just re-assign the value to a new var as follows:
private val _myParameter by parser.flagging(...)
var myParameter = _myParameter
since that seems to confuse the parser and it stops evaluating the rest of the parameters as soon as the first delegate property is accessed. Besides, it is not particularly pretty.
How do you use delegates that don't include a setter in combination with a var property?
Here is how you can wrap a ReadOnlyProperty to make it work the way you want:
class MutableProperty<in R, T>(
// `(R, KProperty<*>) -> T` is accepted here instead of `ReadOnlyProperty<R, T>`,
// to enable wrapping of properties which are based on extension function and don't
// implement `ReadOnlyProperty<R, T>`
wrapped: (R, KProperty<*>) -> T
) : ReadWriteProperty<R, T> {
private var wrapped: ((R, KProperty<*>) -> T)? = wrapped // null when field is assigned
private var field: T? = null
#Suppress("UNCHECKED_CAST") // field is T if wrapped is null
override fun getValue(thisRef: R, property: KProperty<*>) =
if (wrapped == null) field as T
else wrapped!!(thisRef, property)
override fun setValue(thisRef: R, property: KProperty<*>, value: T) {
field = value
wrapped = null
}
}
fun <R, T> ReadOnlyProperty<R, T>.toMutableProperty() = MutableProperty(this::getValue)
fun <R, T> ((R, KProperty<*>) -> T).toMutableProperty() = MutableProperty(this)
Use case:
var lazyVar by lazy { 1 }::getValue.toMutableProperty()
And here is how you can wrap a property delegate provider:
class MutableProvider<in R, T>(
private val provider: (R, KProperty<*>) -> (R, KProperty<*>) -> T
) {
operator fun provideDelegate(thisRef: R, prop: KProperty<*>): MutableProperty<R, T> =
provider(thisRef, prop).toMutableProperty()
}
fun <T> ArgParser.Delegate<T>.toMutableProvider() = MutableProvider { thisRef: Any?, prop ->
provideDelegate(thisRef, prop)::getValue
}
Use case:
var flagging by parser.flagging(
"--my-param",
help = "helptext"
).toMutableProvider()
You could wrap your delegate with a class like this:
class DefaultDelegate<T>(private val default: Delegate<T>){
private var _value: T? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): T? =
_value?: default.value
operator fun setValue(thisRef: Nothing?, property: KProperty<*>, value: T?) {
_value = value
}
}
Usage:
class Foo(parser: ArgParser) {
var myParameter: Boolean? by DefaultDelegate(parser.flagging(
"--my-param",
help = "helptext"
))
}
If you need nullability:
class DefaultDelegate<T>(private val default: Delegate<T>){
private var modified = false
private var _value: T? = null
operator fun getValue(thisRef: Any?, property: KProperty<*>): T? =
if (modified) _value else default.value
operator fun setValue(thisRef: Nothing?, property: KProperty<*>, value: T?) {
_value = value
modified = true
}
}
I am trying to understand how the delegate keyword by works.
So delegating to implemenent an interface is clear e.g.
class Manager(clientele: List<Client> = ArrayList()): List<Client> by clientale
But the following does not work:
data class Client(val name: String, val postalCode: Int)
fun createClient() = Client("Bob", 1234)
val bigClient: Client by createClient() // compilation error
I get the error:
Missing getValue(Nothing?, KProperty<*>) method delegate of type
Client
I thought that if two objects are the same the delegation from one to the other (Client by Client) would work.
Can someone please explain what is the error here and what am I doing wrong?
Unfortunately that's not exactly how delegation of properties works. Based on the documentation:
For a read-only property (i.e. a val), a delegate has to provide a function named getValue that takes the following parameters:
thisRef - must be the same or a supertype of the property owner;
property - must be of type KProperty<*> or its supertype.
For a mutable property (a var), a delegate has to additionally provide a function named setValue that takes the following parameters:
thisRef - same as for getValue();
property - same as for getValue();
newValue - must be of the same type as the property or its subtype.
[...] Both of the functions need to be marked with the operator keyword.
So in order just to make your example work, you have to add a getValue() method which meets the above requirements:
data class Client(val name: String, val postalCode: Int) {
operator fun getValue(thisRef: Nothing?, property: KProperty<*>): Client = this
}
You can also use and implement the ReadOnlyProperty and ReadWriteProperty interfaces which provide the required methods:
data class Client(val name: String, val postalCode: Int) : ReadOnlyProperty<Nothing?, Client> {
override fun getValue(thisRef: Nothing?, property: KProperty<*>): Client = this
}
Edit:
What is this getValue() supposed to do?
Let me explain a little further on a more abstract example. We have the following classes:
class MyDelegate : ReadWriteProperty<MyClass, String> {
private var delegateProperty: String = ""
override fun getValue(thisRef: MyClass, property: KProperty<*>): String {
println("$thisRef delegated getting the ${property.name}'s value to $this")
return delegateProperty
}
override fun setValue(thisRef: MyClass, property: KProperty<*>, value: String) {
println("$thisRef delegated setting the ${property.name}'s value to $this, new value: $value")
delegateProperty = value
}
}
class MyClass {
var property: String by MyDelegate()
}
The above MyClass would get compiled more or less to:
class MyClass {
private var property$delegate: MyDelegate = MyDelegate()
var property: String
get() = property$delegate.getValue(this, this::property)
set(value) = property$delegate.setValue(this, this::property, value)
}
So you can see that the compiler requires a delegate to have getValue() and setValue() methods for mutable properties (var) or only getValue() for immutable properites (val), because it uses them to respectively get and set the delegated property's value.
What are Nothing and KProperty<*>?
KProperty<*> is a Kotlin class which represents a property and provides its metadata.
Nothing is a type that represents a value that doesn't exist. It's quite irrelevant from the delegation point of view. It came up in this case, because you probably defined the bigClient property outside any class so it has no owner, hence thisRef is Nothing.
Given an interface:
interface Countable
{
val count: Int
}
And an implementation/factory:
fun countable(counter: () -> Int): Countable = object : Countable
{
override val count: Int
get() = counter()
}
I can implement this using the class by delegation feature:
class CountableThing : Countable by countable({ 123 })
So that this snippet predictably outputs 123:
fun main()
{
val countableThing = CountableThing()
println(countableThing.count)
}
My question is, in the context of the delegate class, is there any way to get an instance of the delegating receiver?
In other words, can my delegate Countable implementation (the anonymous object defined in fun countable) see/access the receiver instance of the CountableThing class?
I tried this:
fun <T> countable(receiver: T, counter: () -> Int): Countable = object : Countable
{
// ...
}
class CountableThing : Countable by countable<CountableThing>(this, { 123 })
But that's not valid, because expectedly:
class CountableThing : Countable by countable<CountableThing>(this, { 123 })
/^^^^
'this' is not defined in this context
No it can't, delegate objects are just objects they don't even know if they're going to be used to implement an interface through delegation. However, you can consider using delegated properties which are used to delegate properties setters and getters implementations:
class Example {
var p: String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
then you can use
val e = Example()
println(e.p)
which prints:
Example#33a17727, thank you for delegating ‘p’ to me!
As you can see, in your delegate implementation you can use thisRef which is a reference to the object whose property is been delegated.