I am experimenting with Lenses in Kotlin, and I was wondering if there is an elegant way to change multiple attributes at the same time for one object. Let's say my domain looks something like this:
#optics
data class Parameters(
val duration: Int,
val length: Int) {
companion object
}
#optics
data class Calculation(
val product: String
val parameters: Parameters) {
companion object
}
thanks to the #optics annotations, editing single fields is easy to do:
val calculation = Calculation(product = "prod", Parameters(duration = 10, length = 15))
Calculation.product.modify(calculation) { selectedProduct }
Calculation.parameters.duration(calculation) { newDuration() }
Calculation.parameters.length(calculation) { 10 }
These lenses work perfectly in isolation, but what is the right pattern to use when I want to apply the three transformations at once? I can use a var and just overwrite calculation every time, but that does not feel very idiomatic to me.
Arrow currently does not expose such functionality but you can easily write a generic solution yourself.
The snippet below demonstrates how it can be achieved, you can add additional methods to compose from Lens<S, Tuple2<FA, FB>> to Lens<S, Tuple3<FA, FB, FC>> etc.
#optics data class Char(val name: String, val health: Int) {
companion object
}
infix fun <S, FA, FB> Lens<S, FA>.aside(other: Lens<S, FB>): Lens<S, Tuple2<FA, FB>> = object : Lens<S, Tuple2<FA, FB>> {
override fun get(s: S): Tuple2<FA, FB> = Tuple2(this#aside.get(s), other.get(s))
override fun set(s: S, b: Tuple2<FA, FB>): S = other.set(this#aside.set(s, b.a), b.b)
}
fun main() {
val original = Char("", 0)
val charName: Lens<Char, String> = Char.name
val charHealth: Lens<Char, Int> = Char.health
val charNameAndHealth: Lens<Char, Tuple2<String, Int>> = charName.aside(charHealth)
charNameAndHealth.modify(original) { Tuple2("Test", 30) }
}
Related
I have a bunch of lookup tables indexed by key that I would like instantiate lazily (i.e. the tables are expensive to compute and I only expect some of them to be used on any given execution of the code).
private var myLazyMap: Map<KeyClass, TableClass> by lazy { ...}
Doesn't work as that makes the map object itself lazy, which isn't right. I think I may need to write a custom delegate, but I still can't see how to embed that into the map object.
I could wrap TableClass with something like
class LazyTable(val param: TableClassParameter) {
private var table: TableClass by lazy { TableClass(param) }
fun wrappedTableFun(): ResultClass {
return table.tableFun()
}
}
But this does mean the class is wrong and it feels like a hack. Can this be done in a neater way?
It could be implemented in multiple ways, depending on your needs. Probably the easiest is to use a map of lazy values directly:
val map = mutableMapOf<KeyClass, Lazy<TableClass>>()
map[myKey1] = lazy { createTable1() }
map[myKey2] = lazy { createTable2() }
val table = map[myKey1]?.value
If we want to not expose Lazy to the users of the map, we need to create our own LazyMap. One way is to use a map similar to above and just hide Lazy from the user:
class LazyMap<K, V>(
private val map: Map<K, Lazy<V>>
) {
operator fun get(key: K): V? = map[key]?.value
}
Another solution is to use a function that creates values when needed:
class LazyMap<K, V>(
private val compute: (K) -> V
) {
private val map = mutableMapOf<K, V>()
operator fun get(key: K): V? = map.getOrPut(key) { compute(key) }
}
We can also use a separate compute function per each key, as in the answer by #tibtof .
A partial implementation could be something like this:
class LazyMap<K, V>(val lazyVals: Map<K, () -> V>, val cache: MutableMap<K, V> = mutableMapOf()) : Map<K, V> by cache {
companion object {
fun <K, V> lazyMapOf(vararg entries: Pair<K, () -> V>): LazyMap<K, V> = LazyMap(mapOf(*entries))
}
override fun get(key: K): V? = cache[key] ?: lazyVals[key]?.let { cache[key] = it(); cache[key] }
}
fun main() {
val lazyMap = lazyMapOf(
1 to { println("computing one"); "one" },
2 to { println("computing two"); "two" }
)
println("get 2")
println(lazyMap[2])
println("get 1")
println(lazyMap[1])
println("get 0")
println(lazyMap[0])
println("get 2 again")
println(lazyMap[2])
}
An we can observe the lazyness in the output:
get 2
computing two
two
get 1
computing one
one
get 0
null
get 2 again
two
We're trying to do some generic processing in kotlin. Basically, for a given class, we want to get the related Builder object. i.a. for any object that extends a GenericObject, we want a Builder of that Object.
interface Builder<T : GenericObject>
object ConcreteBuilder: Builder<ConcreteObject>
We'd need a function that will return ConcreteBuilder from ConcreteObject
Our current implementation is a Map:
val map = mapOf<KClass<out GenericObject>, Builder<out GenericObject>>(
ConcreteObject::class to ConcreteBuilder
)
Then we can get it with:
inline fun <reified T : GenericObject> transform(...): T {
val builder = map[T::class] as Builder<T>
...
However this isn't very nice as:
we need an explicit cast to Builder<T>
the map has no notion of T, a key and a value could be related to different types.
Is there any better way to achieve it?
A wrapper for the map could be:
class BuilderMap {
private val map = mutableMapOf<KClass<out GenericObject>, Builder<out GenericObject>>()
fun <T: GenericObject> put(key: KClass<T>, value: Builder<T>) {
map[key] = value
}
operator fun <T: GenericObject> get(key: KClass<T>): Builder<T> {
return map[key] as Builder<T>
}
}
This hides the ugliness, while not completely removing it.
To use:
val builderMap = BuilderMap()
builderMap.put(ConcreteObject::class, ConcreteBuilder)
builderMap.put(BetonObject::class, BetonBuilder)
// builderMap.put(BetonObject::class, ConcreteBuilder) – will not compile
val builder = builderMap[T::class]
how can I set properties of a dataclass by its name. For example, I have a raw HTTP GET response
propA=valueA
propB=valueB
and a data class in Kotlin
data class Test(var propA: String = "", var propB: String = ""){}
in my code i have an function that splits the response to a key value array
val test: Test = Test()
rawResp?.split('\n')?.forEach { item: String ->
run {
val keyValue = item.split('=')
TODO
}
}
In JavaScript I can do the following
response.split('\n').forEach(item => {
let keyValue = item.split('=');
this.test[keyValue[0]] = keyValue[1];
});
Is there a similar way in Kotlin?
You cannot readily do this in Kotlin the same way you would in JavaScript (unless you are prepared to handle reflection yourself), but there is a possibility of using a Kotlin feature called Delegated Properties (particularly, a use case Storing Properties in a Map of that feature).
Here is an example specific to code in your original question:
class Test(private val map: Map<String, String>) {
val propA: String by map
val propB: String by map
override fun toString() = "${javaClass.simpleName}(propA=$propA,propB=$propB)"
}
fun main() {
val rawResp: String? = """
propA=valueA
propB=valueB
""".trimIndent()
val props = rawResp?.split('\n')?.map { item ->
val (key, value) = item.split('=')
key to value
}?.toMap() ?: emptyMap()
val test = Test(props)
println("Property 'propA' of test is: ${test.propA}")
println("Or using toString: $test")
}
This outputs:
Property 'propA' of test is: valueA
Or using toString: Test(propA=valueA,propB=valueB)
Unfortunately, you cannot use data classes with property delegation the way you would expect, so you have to 'pay the price' and define the overridden methods (toString, equals, hashCode) on your own if you need them.
By the question, it was not clear for me if each line represents a Test instance or not. So
If not.
fun parse(rawResp: String): Test = rawResp.split("\n").flatMap { it.split("=") }.let { Test(it[0], it[1]) }
If yes.
fun parse(rawResp: String): List<Test> = rawResp.split("\n").map { it.split("=") }.map { Test(it[0], it[1]) }
For null safe alternative you can use nullableString.orEmpty()...
Suppose I've got a sealed class hierarchy like that:
sealed class A {
abstract val x: Int
abstract fun copyX(x1: Int): A
}
data class A1(override val x: Int, val s1: String) : A() {
override fun copyX(x1: Int): A {
return this.copy(x = x1)
}
}
data class A2(override val x: Int, val s2: String) : A() {
override fun copyX(x1: Int): A {
return this.copy(x = x1)
}
}
All the data classes have field x and should provide method copyX(x1: Int) to copy all the fields but x and override x with x1. For instance,
fun foo(a: A): A { a.copyX(100) }
The definitions above probably work but the repeating copyX across all the data classes seem very clumsy. How would you suggest get rid of this repeated copyX ?
First, you can implement copyX as an extension (or even A's member) so as to concentrate the code in one place and avoid at least duplicating the copyX function in the sealed class subtypes:
sealed class A {
abstract val x: Int
}
fun A.copyX(x1: Int): A = when (this) {
is A1 -> copy(x = x1)
is A2 -> copy(x = x1)
}
data class A1(override val x: Int, val s1: String) : A()
data class A2(override val x: Int, val s2: String) : A()
If you have a lot of sealed subtypes and all of them are data classes or have a copy function, you could also copy them generically with reflection. For that, you would need to get the primaryConstructor or the function named copy from the KClass, then fill the arguments for the call, finding the x parameter by name and putting the x1 value for it, and putting the values obtained from component1(), component2() etc. calls or leaving the default values for the other parameters. It would look like this:
fun A.copyX(x1: Int): A {
val copyFunction = this::class.memberFunctions.single { it.name == "copy" }
val args = mapOf(
copyFunction.instanceParameter!! to this,
copyFunction.parameters.single { it.name == "x" } to x1
)
return copyFunction.callBy(args) as A
}
This works because callBy allows omitting the optional arguments.
Note that it requires a dependency on kotlin-reflect and works only with Kotlin/JVM. Also, reflection has some performance overhead, so it's not suitable for performance-critical code. You could optimize this by using the Java reflection (this::class.java, getMethod(...)) instead (which would be more verbose) and caching the reflection entities.
I am trying to implement a QueryBus. Basically, I want to register a list of QueryHandlers. Each QueryHandler implements a handle method defined by an interface. Each QueryHandler is associated to a Query. I want to be able to retrieve a QueryHandler using the Query and call handle on it.
The thing is the handle has to be generic because each QueryHandler handles a Query differently. They all take a dedicated Query and may return whatever they want.
interface Query<R>
interface QueryHandler<R, Q : Query<R>> {
fun handle(query: Q): R
fun listenTo(): String
}
// DTOs
data class BookDto(val name: String)
// List books query
data class ListBooksQuery(val page: Int = 1): Query<List<BookDto>>
class ListBooksQueryHandler: QueryHandler<List<BookDto>, ListBooksQuery> {
override fun handle(query: ListBooksQuery): List<BookDto> {
return listOf(BookDto("Dune"), BookDto("Dune II"))
}
override fun listenTo(): String = ListBooksQuery::class.toString()
}
// Get book query
data class GetBookQuery(val name: String): Query<BookDto?>
class GetBookQueryHandler: QueryHandler<BookDto?, GetBookQuery> {
override fun handle(query: GetBookQuery): BookDto {
return BookDto("Dune")
}
override fun listenTo(): String = GetBookQuery::class.toString()
}
// Run it!
fun main(args: Array<String>) {
// Initializing query bus
val queryHandlers = mapOf(
with(ListBooksQueryHandler()) {this.listenTo() to this},
with(GetBookQueryHandler()) {this.listenTo() to this}
)
val command = ListBooksQuery()
val result = queryHandlers[command::class.toString()].handle(command)
// Should print the list of BookDto
print(result)
}
I don't even know if its possible, to be honest.
UPDATE 1:
I changed the usage example in the main to show what I am really trying to do. The List was for (bad?) demonstration purpose. I want to store the QueryHandlers and retrieve them from a map.
Additional resources:
Here is what I really want to do:
https://gist.github.com/ValentinTrinque/76b7a32221884a46e657090b9ee60193
UPDATE I've read your gist and tried to come up with a solution that will provide a clean interface to the user of the QueryBusMiddleware.
Note that I used objects instead of classes for the QueryHandler implementations, which felt more natural to me (since there is only one possible entry in the map for each Query implementation).
interface Query<R>
interface QueryHandler<R, Q: Query<R>> {
fun handle(query: Q): R
fun listenTo(): String
}
// DTOs
data class BookDto(val name: String)
// List books query
data class ListBooksQuery(val page: Int = 1): Query<List<BookDto>>
object ListBooksQueryHandler: QueryHandler<List<BookDto>, ListBooksQuery> {
override fun handle(query: ListBooksQuery): List<BookDto> {
return listOf(BookDto("Dune"), BookDto("Dune II"))
}
override fun listenTo(): String = ListBooksQuery::class.toString()
}
// Get book query
data class GetBookQuery(val name: String): Query<BookDto?>
object GetBookQueryHandler: QueryHandler<BookDto?, GetBookQuery> {
override fun handle(query: GetBookQuery): BookDto {
return BookDto("Dune")
}
override fun listenTo(): String = GetBookQuery::class.toString()
}
// Run it!
fun main(args: Array<String>) {
// Initializing query bus
val queryHandlers = listOf(
ListBooksQueryHandler,
GetBookQueryHandler
)
val dispatcher: QueryBusMiddleware = QueryDispatcherMiddleware(queryHandlers)
// Calling query bus
val query = ListBooksQuery()
// Result should be List<BookDto>
val result = dispatcher.dispatch(query)
print(result)
}
interface QueryBusMiddleware {
fun <R, Q : Query<R>> dispatch(query: Q): R
}
class QueryDispatcherMiddleware constructor(handlers: List<QueryHandler<*, *>>) : QueryBusMiddleware {
private val handlers = HashMap<String, QueryHandler<*, *>>()
init {
handlers.forEach { handler -> this.handlers[handler.listenTo()] = handler }
}
override fun <R, Q : Query<R>> dispatch(query: Q): R {
val queryClass = query::class.toString()
val handler = handlers[queryClass] ?: throw Exception("No handler listen to the query: $queryClass")
return handler::class.members.find { it.name == "handle" }!!.call(handler, query) as R
}
}