¿Es posible invocar una función con un unaryPlus en kotlin?
Esta es una pregunta de seguimiento sobre otra pregunta que hice ayer.
¿Cómo puedo crear una lista anidada usando un patrón de construcción?
Crédito a: Pelocho por dar una buena respuesta.
He utilizado este tutorial para hacer un tipo de seguro graphQL generador de consultas:
Lo que quiero hacer ahora es simplificar lo que hice. Y sé que kotlin debe tener algunas características interesantes para hacerlo.
Ahora mismo tengo que invocar funciones cuando quiero agregar una entidad a mi consulta:
fun main() {
events{
title() // I don't like to do () when it is an edge case
}
}
Me pregunto si es posible hacer algo diferente y más simple, como:
fun main() {
events{
title // this is much nicer
}
}
Pero si no es posible, entonces está bien reducir la cantidad de caracteres con un operador sobrecargado como por ejemplo.unaryPlus
fun main() {
events{
+title // this is also nicer
}
}
Mi código que tengo ahora tiene una clase llamada Query
donde pongo el método:
operator fun Query.unaryPlus() {
visitEntity(this,{})
}
Pero no parece funcionar para mí.
Todo mi código está aquí.
interface Element {
fun render(builder: StringBuilder, indent: String)
}
@DslMarker //Domain Specific Language
annotation class GraphQLMarker
@GraphQLMarker
abstract class Query(val name: String) : Element {
val children = arrayListOf<Element>()
protected fun <T : Element> visitEntity(entity: T, visit: T.() -> Unit = {}): T {
entity.visit()
children.add(entity)
return entity
}
override fun render(builder: StringBuilder, indent: String) {
builder.append("$indent$name")
if (children.isNotEmpty()) {
builder.append("{\n")
for (c in children) {
c.render(builder, "$indent ") } builder.append("$indent}")
}
builder.append("\n")
}
operator fun Query.unaryPlus() {
visitEntity(this,{})
}
override fun toString(): String {
val builder = StringBuilder()
render(builder, "")
return builder.toString()
}
}
Luego creé las clases relevantes para mi caso
class Filter private constructor(val filters: MutableMap<FilterType, Any>) {
class Builder {
private val filters = mutableMapOf<FilterType, Any>()
fun filters(key: FilterType, value: Any) = apply {
this.filters[key] = value
}
fun build(): Filter {
return Filter(filters)
}
}
}
class EVENTS(private val filter: Filter) : Query("events") {
override fun render(builder: StringBuilder, indent: String) {
builder.append("{$name") if (filter.filters.isNotEmpty()) { builder.append("(" + filter.filters.map { if (it.value is Int || it.value is Long) { it.key.str + ":" + it.value + "," } else { it.key.str + ":\"" + it.value + "\"," } }.joinToString(" ").dropLast(1) + ")") } if (children.isNotEmpty()) { builder.append("{\n") for (c in children) { c.render(builder, "$indent ")
}
builder.append("$indent}")
}
builder.append("\n}")
}
fun title() = visitEntity(TITLE())
fun genre() = visitEntity(GENRE())
fun image() = visitEntity(IMAGE())
fun link() = visitEntity(LINK())
fun other() = visitEntity(OTHER())
fun price() = visitEntity(PRICE())
fun text() = visitEntity(TEXT())
fun tickets() = visitEntity(TICKETS())
fun time() = visitEntity(TIME())
fun location(visit: LOCATION.() -> Unit) = visitEntity(LOCATION(), visit)
}
class TITLE : Query("title")
class GENRE : Query("genre")
class IMAGE : Query("image")
class LINK : Query("link")
class OTHER : Query("other")
class PRICE : Query("price")
class TEXT : Query("text")
class TICKETS : Query("tickets")
class TIME : Query("time")
class LOCATION : Query("location") {
fun area() = visitEntity(AREA())
fun place() = visitEntity(PLACE())
fun address(visit: ADDRESS.() -> Unit) = visitEntity(ADDRESS(), visit)
fun coordinates(visit: COORDINATES.() -> Unit) = visitEntity(COORDINATES(), visit)
}
class AREA : Query("area")
class PLACE : Query("place")
class ADDRESS : Query("address") {
fun city() = visitEntity(CITY())
fun street() = visitEntity(STREET())
fun no() = visitEntity(NO())
fun state() = visitEntity(STATE())
fun zip() = visitEntity(ZIP())
}
class CITY : Query("city")
class STREET : Query("street")
class NO : Query("no")
class STATE : Query("state")
class ZIP : Query("zip")
class COORDINATES : Query("coordinates") {
fun longitude() = visitEntity(LONGITUDE())
fun latitude() = visitEntity(LATITUDE())
}
class LONGITUDE : Query("longitude")
class LATITUDE : Query("latitude")
enum class FilterType(val str: String) {
PLACE("place"),
PRICELT("priceLT"),
PRICEGT("priceGT"),
TIMELT("timestampLT"),
TIMEGT("timestampGT"),
AREA("area"),
TITLE("title"),
GENRE("genre")
}
Algunas de las clases son edgecases y no se encajarán más abajo. Entonces estaba pensando si es posible simplificar eso un poco. Y si puedo usar a unaryPlus
para invocarlos cuando los llamo desde la main()
función, entonces no tengo que escribir {}
después de un solo caso de borde, cuando no tienen anidamiento de ninguna manera
fun main(){
events(filter) {
title()
genre()
image()
link()
tickets()
other()
price()
text()
time()
location {
area()
place()
address {
city()
street()
no()
state()
zip()
}
coordinates {
longitude()
latitude()
}
}
}
}
Gracias de antemano. ¡Que tengas un buen día!
Respuestas
Sí, puedes usarlo unaryPlus
para lo que quieras
Consulte la documentación de sobrecarga del operador
Aquí tienes un ejemplo de trabajo:
fun main() {
val events = events {
+title
+genre
}
println(events.render())
}
interface Element {
fun render() : String
}
@DslMarker //Domain Specific Language
annotation class GraphQLMarker
@GraphQLMarker
fun events(init: EventBody.() -> Unit): Element {
val events = EventBody()
events.init()
return events
}
class EventBody: Element {
override fun render() =
"events:${elements.joinToString { it.render() }}"
private val elements = mutableListOf<Element>()
operator fun Element.unaryPlus() = elements.add(this)
}
@GraphQLMarker
object title: Element {
override fun render() = "title"
}
@GraphQLMarker
object genre: Element {
override fun render() = "genre"
}
que salidas
events:title, genre