Using Kotlin type-safe builders one might end up writing this code
code {
dict["a"] = "foo"; // dict is a Map hidden inside that can associate some name to some value
println(dict["a"]); // usage of this value
}
This code is ok, but there is a problem: "a" is just a string. I want it to be like a user-defined variable - an identifier that is recognized by the compiler, auto-complete enabled.
Is there a way to turn it into something like this?
code {
a = "foo"; // now 'a' is not a Map key, but an identifier recognized by Kotlin as a variable name
println(a);
}
I can do this if I make code's lambda an extension function over some object with a field a defined inside. This is not what I want. I want to be able to use other variables (with unknown names) as well.
A possible workaround could be
code {
var a = v("a", "foo");
println(a);
}
Where v is a method of the extension's object, that stores value "foo" inside "dict" and also returns a handle to this value.
This case is almost perfect, but can it be clearer/better somehow?
You can use property delegation:
class Code {
private val dict = mutableMapOf<String, String>()
operator fun String.provideDelegate(
thisRef: Any?,
prop: KProperty<*>
): MutableMap<String, String> {
dict[prop.name] = this
return dict
}
}
Use case:
code {
var a by "foo" // dict = {a=foo}
println(a) // foo
a = "bar" // dict = {a=bar}
println(a) // bar
}
Related
How can I force a compile-time error, when somebody tries to use a function as expression that is not intended to be used like that?
fun someFunctionThatReturnsNothing() { println("Doing some stuff") }
// this should give an error:
val value = someFunctionThatReturnsNothing()
My use-case is generating a DSL where there can be name-clashes between DSL builders and sub DSLs in other builders depending on the scope of execution - e.g.:
// this is valid, calling RequestDSL.attribute(...) : Unit here,
val myRequest = request {
attribute {
name = "foo"
value = "bar"
}
}
// this is valid, calling AttributeDSLKt.attribute(...) : Attribute
val special = attribute {
name = "special"
value = "ops"
}
val myRequest = request {
extraAttribute = special
}
// this does not compile, but is confusing,
// because the compiler does not complain where the error was made
val myRequest = request {
// the user intends to call AttributeKt.attribute(...) : Attribute,
// but the compiler can only call RequestDSL.attribute(...) : Unit here
val special = attribute {
name = "special"
value = "ops"
}
// this is confusing and should already have been prevented above:
// >> Type mismatch. Required: Attribute. Found: Unit. <<
extraAttribute = special
}
If I could do something like RequestDSL.attribute(...) : void, the user wouldn't even be allowed to call attribute(...) as an expression inside the DSL. That would avoid the issue.
Can this be done somehow?
I tried Nothing, but it just makes all code unreachable after the function call.
I also tried Void, but it just force me to make the return nullable and return null instead of giving an error on the call side.
Suppose I have two methods:
private fun method1(a: A): A {
return a.copy(v1 = null)
}
private fun method2(a: A): A {
return a.copy(v2 = null)
}
Can I write something like:
private fun commonMethod(a: A, variableToChange: String): A {
return a.copy($variableToChange = null)
}
Another words, can I use a variable to refer to a named argument?
If I understand correctly what you are trying to archive I would recommend to pass a setter to the method e.g.
fun <A> changer (a: A, setter: (a: A) -> Unit ) {
// do stuff
setter(a)
}
Is this what you are looking for?
A possible solution for this problem (with usage of reflection) is:
inline fun <reified T : Any> copyValues(a: T, values: Map<String, Any?>): T {
val function = a::class.functions.first { it.name == "copy" }
val parameters = function.parameters
return function.callBy(
values.map { (parameterName, value) ->
parameters.first { it.name == parameterName } to value
}.toMap() + (parameters.first() to a)
) as T
}
This works with all data classes and all classes that have a custom copy function with the same semantics (as long as the parameter names are not erased while compiling). In the first step the function reference of the copy method is searched (KFunction<*>). This object has two importent properties. The parameters property and the callBy function.
With the callBy function you can execute all function references with a map for the parameters. This map must contain a reference to the receiver object.
The parameters propery contains a collection of KProperty. They are needed as keys for the callBy map. The name can be used to find the right KProperty. If a function as a parameter that is not given in the map it uses the default value if available or throws an exception.
Be aware that this solution requires the full reflection library and therefore only works with Kotlin-JVM. It also ignores typechecking for the parameters and can easily lead to runtime exceptions.
You can use it like:
data class Person (
val name: String,
val age: Int,
val foo: Boolean
)
fun main() {
var p = Person("Bob", 18, false)
println(p)
p = copyValues(p, mapOf(
"name" to "Max",
"age" to 35,
"foo" to true
))
println(p)
}
// Person(name=Name, age=15, foo=false)
// Person(name=Max, age=35, foo=true)
I've seen lots of tutorials but still didn't get exactly how it works. I understood the main idea: a function holding functions with data, but looking official documentation I couldn't realize how and where the data is stored and who calls the function responsible for its storaging. Other tutorials seems to show just a snippet of code, which didn't help me much. Can you give me a full and simple example with a trivial class, like a person, please?
I was interested in some details, too. Here's what I wrote:
data class Person(
var name: String? = null,
var age: Int? = null,
val children: MutableList<Person> = ArrayList()
) {
fun child(init: Person.() -> Unit) = Person().also {
it.init()
children.add(it)
}
}
fun person(init: Person.() -> Unit) = Person().apply { init() }
fun main(args: Array<String>) {
val p = person {
name = "Mommy"
age = 33
child {
name = "Gugu"
age = 2
}
child {
name = "Gaga"
age = 3
}
}
println(p)
}
It prints out (with a little formatting added):
Person(name=Mommy, age=33, children=[
Person(name=Gugu, age=2, children=[]),
Person(name=Gaga, age=3, children=[])
])
Kotlin DSLs
Kotlin is great for writing your own Domain Specific Languages, also called type-safe builders. Anko is one of the examples using such DSLs. The most important language feature you need to understand here is called "Function Literals with Receiver", which you made use of already: Test.() -> Unit
Function Literals with Receiver - Basics
Kotlin supports the concept of “function literals with receivers”. This enables us to call methods on the receiver of the function literal in its body without any specific qualifiers. This is very similar to extension functionsin which it’s also possible to access members of the receiver object inside the extension.
A simple example, also one of the greatest functions in the Kotlin standard library, isapply
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
As you can see, such a function literal with receiver is taken as the argument block here. This block is simply executed and the receiver (which is an instance of T) is returned. In action this looks as follows:
val foo: Bar = Bar().apply {
color = RED
text = "Foo"
}
We instantiate an object of Bar and call apply on it. The instance of Bar becomes the “receiver”. The block, passed as an argument in {}(lambda expression) does not need to use additional qualifiers to access and modify the shown visible properties color and text.
Function Literals with Receiver - in DSL
If you look at this example, taken from the documentation, you see this in action:
class HTML {
fun body() { ... }
}
fun html(init: HTML.() -> Unit): HTML {
val html = HTML() // create the receiver object
html.init() // pass the receiver object to the lambda
return html
}
html { // lambda with receiver begins here
body() // calling a method on the receiver object
}
The html() function expects such a function literal with receiver with HTML as the receiver. In the function body you can see how it is used: an instance of HTML is created and the init is called on it.
Benefit
The caller of such an higher-order function expecting a function literal with receiver (like html()) you can use any visible HTML function and property without additional qualifiers (like this e.g.), as you can see in the call:
html { // lambda with receiver begins here
body() // calling a method on the receiver object
}
Example
I've written a sample DSL and described it in a blog post. Maybe that's also helpful.
Just to add other syntaxe
data class QCMBean(var qcmId : Int=-1, var question : String = "", var answers : ArrayList<AnswerBean> = ArrayList()) {
companion object {
fun qcm(init:QCMBean.()->Unit) = QCMBean().apply {
init()
}
}
fun answer(answer:String = "") = AnswerBean(answer).apply {
answers.add(this)
}
}
data class AnswerBean(var answer:String = "")
qcm {
qcmId = 1
question = "How many cat ?"
answer("1")
answer("2")
}
some background:
val (name, age) = person
This syntax is called a destructuring declaration. It creates multiple variables (correction, creates multiple values) at at the same time.
Destructuring declarations also work in for-loops: when you say:
for ((a, b) in collection) { ... }
Lets take a look at a list item i have:
#Parcelize
data class MyModel(
var name: String = "",
var is_locked: Boolean = true,
var is_one_size: Boolean = false,
) : Parcelable
and now i have obtained a list of "MyModel" classes and i am trying to loop over them like this:
private fun initMyModelList(model: MutableList<MyModel>) {
//i want to access is_locked from here with destruction but i cant ? IDE telling me the type is an int but its clearly defined as a Boolean
for((is_locked) in model){
//what i want to do in here is access the is_locked var of the model list and change all of them in a loop. im trying to use Destructuring in loop as a conveience. why is it not working ?
//how can i make the call signature look like this--- > is_locked = true instad of model.is_locked =true
}
}
all i want to do is be able to call is_locked = true instead of model.is_locked = true within the loop. how can this be done ?
This syntax is called a destructuring declaration. It creates multiple variables at at the same time.
It doesn't create multiple variables, it captures multiple values. You're working with values, not references, as your source tells further:
A destructuring declaration is compiled down to the following code:
val name = person.component1()
val age = person.component2()
Closest to what you want would be this custom extension function:
inline fun <E> Iterable<E>.withEach(block: E.() -> Unit) {
forEach {
it.block()
}
}
Use like so:
model.withEach {
is_locked = true
}
Before you ask the obligatory question "why isn't this included in stdlib?" consider that functional style programming typically is about transforming immutable types. Basically, what I did here was encourage a bad habit.
Basically, it isn't possible, cause your code is compiled to something like:
for (m in models) {
val is_locked = m.component1()
...
}
Which means that you create a local property which cannot be reassigned. But you can do something like this:
for (m in model) {
with(m) {
is_locked = true
}
}
Yep, it isn't perfect, but it can be improved with extension methods:
fun <T> List<T>.forEachApply(block: T.() -> Unit) {
forEach(block)
}
private fun initMyModelList(model: MutableList<MyModel>) {
model.forEachApply {
is_locked = true
}
}
You can use destructuring in a loop just fine as read-only values.
data class Stuff(val name: String, val other: String)
fun doStuff() {
val stuff = Stuff("happy", "day")
val stuffs = listOf(stuff)
for ((name) in stuffs) {
println(name)
}
}
Running that method prints "happy" to the console. Baeldung shows an example of using it here.
It's best practice for data classes to be immutable, so I would try to rewrite your data class to be immutable. The .copy function will let you copy your data class but with new, different values.
I would like to do something like this but I have no idea, and I can't use js() to insert any dynamic data into because js() only takes constant string parameters (or is there a way to do that?)
val doc: dynamic = Any()
doc._id = name
data.forEach {
it.forEach { entry ->
// need to set property of the doc using entry.key as the property name with entry.value
}
}
you can using indexed access just like as javascript bracket access notation, for example:
val doc: dynamic = Any()
doc._id = name
data.forEach {
it.forEach { entry ->
// v--- kotlin process the brackets []= as a set operator
doc[entry.key] = entry.value;
}
}