Chaining property references and maintaining both the ability to read and write - kotlin

Using a KMutableProperty1 to access a classes property works both as a getter and setter.
class BaseClass(
var baseInt: Int = 0,
var baseInnerClass: InnerClass = InnerClass()
)
class InnerClass(
var innerInt: Int = 0,
)
val target = BaseClass()
val kMutableProperty1b = (BaseClass::baseInt)
kMutableProperty1b.set(target, 4)
val baseInt = kMutableProperty1b.get(target)
To be able to access nested properties like
BaseClass::innerClass -> InnerClass:innerInt
I tried to up chain two kMutableProperty1 with
fun <A, B, C> ((A) -> B).chained(getter : (B) -> C) : (A) -> C = { getter(this(it)) }
With that, the inner properties can be read, but not set:
val chainedKMutableProperty = baseMutableProperty.chained(InnerClass::innerInt)
val innerInt = chainedKMutableProperty(target)
chainedKMutableProperty.set(target, 5) // Not available
In Swift something similar can be achieved using KeyPaths
let target = BaseClass()
let aKeyPath = \BaseClass.baseInt
target[keyPath: aKeyPath] = 4
let baseInt = target[keyPath: aKeyPath]
let bKeyPath = \BaseClass.baseInnerClass
let chainedKeyPath = bKeyPath.appending(path: \InnerClass.innerInt)
let innerInt = target[keyPath: chainedKeyPath]
target[keyPath: chainedKeyPath] = 5
How can I do the same in Kotlin - chaining property accessors and maintaining both the ability to read and write?

I don't think there is something like this already in Kotlin or Java stdlib. We can easily create it by ourselves, although I don’t think it is a good idea to stick to KProperty. This interface isn’t just a generic accessor interface. It is a very specific thing: a property of a class. And we don't deal with class properties here.
Instead, I suggest to create our own interfaces. Below is a simple POC:
fun main() {
val target = BaseClass()
val chainedProp = BaseClass::baseInnerClass chain InnerClass::innerInt
println(chainedProp.get(target))
chainedProp.set(target, 5)
// or
println(target[chainedProp])
target[chainedProp] = 12
}
operator fun <T, V> T.get(key: MyProperty<T, V>): V = key.get(this)
operator fun <T, V> T.set(key: MyMutableProperty<T, V>, value: V) = key.set(this, value)
infix fun <T, V, V2> KProperty1<T, V>.chain(next: KMutableProperty1<V, V2>): MyMutableProperty<T, V2> = asMyProperty() chain next.asMyProperty()
infix fun <T, V, V2> MyProperty<T, V>.chain(next: MyMutableProperty<V, V2>): MyMutableProperty<T, V2> = object : MyMutableProperty<T, V2> {
override fun get(receiver: T): V2 {
return next.get(this#chain.get(receiver))
}
override fun set(receiver: T, value: V2) {
next.set(this#chain.get(receiver), value)
}
}
fun <T, V> KProperty1<T, V>.asMyProperty(): MyProperty<T, V> = object : MyProperty<T, V> {
override fun get(receiver: T): V {
return this#asMyProperty.get(receiver)
}
}
fun <T, V> KMutableProperty1<T, V>.asMyProperty(): MyMutableProperty<T, V> = object : MyMutableProperty<T, V> {
override fun get(receiver: T): V {
return this#asMyProperty.get(receiver)
}
override fun set(receiver: T, value: V) {
this#asMyProperty.set(receiver, value)
}
}
interface MyProperty<in T, out V> {
fun get(receiver: T): V
}
interface MyMutableProperty<in T, V> : MyProperty<T, V> {
fun set(receiver: T, value: V)
}

Related

How to use function receiver type with SAM interfaces in Kotlin

I'm coming from Java and am new to Kotlin. I try to understand how to use receiver type with lambdas specified as functional SAM interfaces.
Let the code speak for itself.
fun interface Delegator <T> {
fun delegate(receiver: T)
}
fun <T> invokeWithDynamicReceiver(receiver: T, fn: T.() -> Unit) = receiver.fn()
fun <T> invokeWithSamInterface(receiver: T, fn: Delegator<T>) = fn.delegate(receiver)
fun dynamicReceiver() {
invokeWithDynamicReceiver("Foo") { length } // Dynamic receiver
invokeWithSamInterface("Foo") { it.length } // Can't bind receiver as "this"
}
How do I need to change the code to use the Delegator lambda with dynamic receiver?
You can define the functions inside the Delegator as extension function, this way the receiver is passed as this to the lambda.
fun interface ExtensionDelegator <T, R> {
fun T.delegate(): R
}
fun <T, R> invokeWithExtensionSamInterface(receiver: T, fn: ExtensionDelegator<T, R>): R =
with(fn) { receiver.delegate() }
Alternatively, you can simply define the dynamic receiver with a typealias to achieve the same result.
typealias AliasDelegator<T, R> = T.() -> R
fun <T, R> invokeWithAliasSamInterface(receiver: T, fn: AliasDelegator<T, R>): R = fn.invoke(receiver)
On the use site, both approaches look the same.
fun main() {
val result = invokeWithExtensionSamInterface("Foo") { length }
println(result)
val otherResult = invokeWithAliasSamInterface("Fizz") { length }
println(otherResult)
}

Add a side-effect to a function in a generic way

How can I write a Kotlin generic function that takes a function as an argument and adds a side-effect to it? For instance,
fun something(one: Int, two: String): String { return "${one}, ${two}" }
fun somethingElse(arg: Array<String>): String { return "${arg}" }
val w1 = wrapped(::something)
w1(42, "hello")
val w2 = wrapped(::somethingElse)
w2(arrayOf("ichi", "ni"))
The following works for functions that take only a single parameter:
fun <A, R> wrapped(theFun: (a: A) -> R): (a: A) -> R {
return { a: A ->
theFun(a).also { println("wrapped: result is $it") }
}
}
To make this work with an arbitrary number of arguments, I'd need some construct that gives me the type of the argument list. Unfortunately, the Function generic can't be used since it takes only one parameter. The following does not compile:
fun <A, R> wrapped(theFun: Function<A, R>): Function<A, R> {
return { args: A ->
theFun(*args).also { println("wrapped: result is ${it}") }
}
}
Or maybe I could use varargs? Does not seem to work with lambdas. Or Kotlin reflection?
Solution using reflection:
class KFunctionWithSideEffect<R>(private val f: KFunction<R>, private val sideEffect: (R) -> Unit) : KFunction<R> by f {
override fun call(vararg args: Any?) = f.call(*args).also { sideEffect(it) }
override fun callBy(args: Map<KParameter, Any?>) = f.callBy(args).also { sideEffect(it) }
}
fun <R> wrapped(theFun: KFunction<R>, sideEffect: (R) -> Unit = { str -> println("wrapped: result is $str") }) =
KFunctionWithSideEffect(theFun, sideEffect)
Usage:
val w1 = wrapped(::something)
w1.call(42, "hello")
val w2 = wrapped(::somethingElse)
w2.call(arrayOf("ichi", "ni"))

How to nest multiple property delegates in Kotlin

I've come accross a case where I want to "chain" mutliple delegates (piping the output of one into the other).
This seems to be possible:
private val errorLogList by listSO(listOf<StateObject<Luxeption>>(), SODest.NONE, publicSOAccessRights())
val errorLog: StateObject<List<StateObject<Luxeption>>> by addToSet(errorLogList)
However, this does not look too well :). I'd like to do it in one line like this:
val errorLog: StateObject<List<StateObject<Luxeption>>> by addToSet(
listSO(listOf<StateObject<Luxeption>>(), SODest.NONE, publicSOAccessRights())
)
My question: Is this type of creating properties through delegates possible in Kotlin?
Here are both implementations of my delegates:
addToSet:
open class ChildSOReturner {
val set: Set<StateObject<*>> = setOf()
inline fun <reified T> addToSet(so: T) = object: ReadOnlyProperty<Any?, T> {
override operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
if (thisRef is T) {
set.plus(so)
return so
} else throw IllegalArgumentException()
}
}
}
listSo:
fun <O> listSO(
initialState: List<StateObject<O>>,
soDest: SODest,
soAccessRights: SOAccessRights
) = object : ReadOnlyProperty<Any?, StateObject<List<StateObject<O>>>> {
override operator fun getValue(thisRef: Any?, property: KProperty<*>): StateObject<List<StateObject<O>>> {
val meta = SOMeta(SOId(property.name), soDest, soAccessRights)
return StateObjectList(initialState, meta)
}
}
It turned out to be quite tricky, but possible (unless I am missing something, and it isn't tested but the idea should work):
fun <T, U, V> composeProperties(prop: ReadOnlyProperty<T, U>, f: (U) -> ReadOnlyProperty<T, V>) : ReadOnlyProperty<T, V> {
var props = mutableMapOf<Pair<T, KProperty<*>>, ReadOnlyProperty<T, V>>()
return object : ReadOnlyProperty<T, V> {
override operator fun getValue(thisRef: T, property: KProperty<*>): V {
val prop1 = props.getOrPut(Pair(thisRef, property)) {
f(prop.getValue(thisRef, property))
}
return prop1.getValue(thisRef, property)
}
}
}
And then to use
val errorLog: ... by composeProperties(listSO(...)) { addToSet(it) }

Kotlin method generic type verification

I'm trying to write a method that takes a KProperty1 and a object of R like so
inline fun <T: Any, R: Any> List<T>.test(prop1: KProperty1<T, R>, prop2: R): List<T>
except I'm not getting type checking on the prop2. Is there any way to ensure that prop2 is of type R?
Here is a more complete example
class Foo
class Bar(val foo: Foo)
fun main(args: Array<String>): Unit {
val list = listOf(Bar(Foo()))
list.test(Bar::foo, Foo()) // This should work
list.test(Bar::foo, "") // I want this to be a type error since a string is not a Foo
}
inline fun <T: Any, R: Any> List<T>.test(prop1: KProperty1<T, R>, prop2: R): List<T> {
println(prop1.invoke(this.first())::class == prop2::class)
return listOf()
}
If you want to restrict R to children of Foo then provide upper bound constraint:
inline fun <T: Any, R: Foo> List<T>.test(prop1: KProperty1<T, R>, prop2: R): List<T> {
println(prop1.invoke(this.first())::class == prop2::class)
return listOf()
}

Not nullable Mutable Map

Java : 1.8.0_102
Kotlin: 1.0.4
I'm trying to create a map where you can do something like map["key"] += 5 similar to javascript.
Kotlin already has withDefault that solves one part of this, but map's get function still returns a nullable value, so i proceeded to make my own implementation of this inspired by withDefault
interface NonNullableMutableMap<K,V> : MutableMap<K,V> {
override fun put(key: K, value: V): V
override fun get(key: K): V
}
fun <K,V> MutableMap<K,V>.withoutNullValues(default: () -> V): NonNullableMutableMap<K, V> {
return NonNullableMapWrapper(this, default)
}
class NonNullableMapWrapper<K,V>(val map: MutableMap<K,V>, val default: () -> V) : NonNullableMutableMap<K,V> {
override fun put(key: K, value: V): V = map.put(key, value) ?: default()
override fun get(key: K): V {
val value = map.getOrPut(key, default)
return value
}
override val size: Int get() = map.size
override fun containsKey(key: K): Boolean = map.containsKey(key)
override fun containsValue(value: V): Boolean = map.containsValue(value)
override fun isEmpty(): Boolean = map.isEmpty()
override val entries: MutableSet<MutableMap.MutableEntry<K, V>> get() = map.entries
override val keys: MutableSet<K> get() = map.keys
override val values: MutableCollection<V> get() = map.values
override fun clear() {
map.clear()
}
override fun putAll(from: Map<out K, V>) {
map.putAll(from)
}
override fun remove(key: K): V {
return map.remove(key) ?: default()
}
}
I created the following unit test to test it
class NonNullableMapTest {
#Test
fun notNullableTest() {
val map = HashMap<String, Long>().withoutNullValues { 0 }
map["first"] += 10L
map["second"] -= 10L
assertThat(map["first"]).isEqualTo(10L)
assertThat(map["second"]).isEqualTo(-10L)
assertThat(map["third"]).isEqualTo(0L)
}
}
But i'm getting the following error when i run the test:
tried to access method kotlin.collections.MapsKt__MapsJVMKt.set(Ljava/util/Map;Ljava/lang/Object;Ljava/lang/Object;)V from class foo.bar.NonNullableMapTest
java.lang.IllegalAccessError: tried to access method kotlin.collections.MapsKt__MapsJVMKt.set(Ljava/util/Map;Ljava/lang/Object;Ljava/lang/Object;)V from class foo.bar.NonNullableMapTest
Any idea how to resolve this issue?
This looks like a bug to me. I recommend reporting it at Kotlin (KT) | YouTrack.
One way to workaround it is by explicitly defining set on your NonNullableMutableMap interface. e.g.:
interface NonNullableMutableMap<K, V> : MutableMap<K, V> {
override fun put(key: K, value: V): V
override fun get(key: K): V
operator fun set(key: K, value: V) {
put(key, value)
}
}
Regarding to the runtime error you get, there is currently a bug in how += operator gets compiled for inline MutableMap.set extension function: https://youtrack.jetbrains.com/issue/KT-14227
The workaround is not to use +=:
map["first"] = map["first"] + 10L