Destructuring props passed to functional component in kotlin - kotlin

In JavaScript, the destructuring of an object is something common.
const foo = {
a: 1
b: 2
c: 3
};
const {a, b, c } = foo;
console.log(a)
1
Is something like this possigle with KotlinJS React?
interface FooProps : Props {
var a: Int
var b: Int
var c: Int
}
val Foo = FC<FooProps> { props ->
val(a, b, c) = props
...
}
This is not working. It gives me
Destructuring declaration initializer of type FooProps must have a 'component1()' function

Kotlin supports destructuring declarations, however they work in a different way than JavaScript.
In particular, you can destructure an object like this:
val (property1, property2, property3, ..., propertyN) = object
assuming that object contains certain methods:
operator fun component1()
operator fun component2()
...
operator fun componentN()
Example:
class Person(val name: String, val dateOfBirth: LocalDate) {
operator fun component1(): String = name
operator fun component2(): LocalDate = dateOfBirth
}
val johnDoe = Person("John", LocalDate.of(1980, JANUARY, 1))
val (name, dob) = johnDoe
println("$name -> $dob") // prints John -> 1980-01-01
Use can make use of extension functions to implement this behaviour on classes you don't own. Example:
operator fun String.component1(): Int = this.length
operator fun String.component2(): Char = this.first()
val someString = "Hello, world"
val (length, firstChar) = someString
println("$length -> $firstChar") // prints 12 -> H

Related

kotlin data class constructors not getting picked up

I am creating a data class in kotlin as such
data class User(val name: String, val age: Int)
{
constructor(name: String, age: Int, size: String): this(name, age) {
}
}
In my main function, I can access the objects as such:
fun main(){
val x = User("foo", 5, "M")
println(x.name)
println(x.age)
println(x.size) // does not work
}
My problem is that I can't get access to size.
What I am trying to do is, create a data class where top level params are the common items that will be accessed, and in the constructors, have additional params that fit certain situations. The purpose is so that I can do something like
// something along the lines of
if (!haveSize()){
val person = User("foo", 5, "M")
} else {
val person = User("foo", 5)
}
}
Any ideas?
In Kotlin you do not need separate constructors for defining optional constructor params. You can define them all in a single constructor with default values or make them nullable, like this:
data class User(val name: String, val age: Int, val size: String = "M")
fun main(){
val x = User("foo", 5, "L")
val y = User("foo", 5)
println(x.size) // "L" from call site
println(y.size) // "M" from default param
}
You can not access size variable, because this is from secondary construct, but we have alternative variant.
data class User(var name: String, var age: Int) {
var size: String
init {
size = "size"
}
constructor(name: String, age: Int, size: String) : this(name, age) {
this.size = size
}
}
In short, you want to have one property that can be one of a limited number of options. This could be solved using generics, or sealed inheritance.
Generics
Here I've added an interface, MountDetails, with a generic parameter, T. There's a single property, val c, which is of type T.
data class User(
val mountOptions: MountOptions,
val mountDetails: MountDetails<*>,
)
data class MountOptions(
val a: String,
val b: String
)
interface MountDetails<T : Any> {
val c: T
}
data class MountOneDetails(override val c: Int) : MountDetails<Int>
data class MountTwoDetails(override val c: String) : MountDetails<String>
Because the implementations MountDetails (MountOneDetails and MountTwoDetails) specify the type of T to be Int or String, val c can always be accessed.
fun anotherCaller(user: User) {
println(user.mountOptions.a)
println(user.mountOptions.b)
println(user.mountDetails)
}
fun main() {
val mt = MountOptions("foo", "bar")
val mountOneDetails = MountOneDetails(111)
anotherCaller(User(mt, mountOneDetails))
val mountTwoDetails = MountTwoDetails("mount two")
anotherCaller(User(mt, mountTwoDetails))
}
Output:
foo
bar
MountOneDetails(c=111)
foo
bar
MountTwoDetails(c=mount two)
Generics have downsides though. If there are lots of generic parameters it's messy, and it can be difficult at runtime to determine the type of classes thanks to type-erasure.
Sealed inheritance
Since you only have a limited number of mount details, a much neater solution is sealed classes and interfaces.
data class User(val mountOptions: MountOptions)
sealed interface MountOptions {
val a: String
val b: String
}
data class MountOneOptions(
override val a: String,
override val b: String,
val integerData: Int,
) : MountOptions
data class MountTwoOptions(
override val a: String,
override val b: String,
val stringData: String,
) : MountOptions
The benefit here is that there's fewer classes, and the typings are more specific. It's also easy to add or remove an additional mount details, and any exhaustive when statements will cause a compiler error.
fun anotherCaller(user: User) {
println(user.mountOptions.a)
println(user.mountOptions.b)
// use an exhaustive when to determine the actual type
when (user.mountOptions) {
is MountOneOptions -> println(user.mountOptions.integerData)
is MountTwoOptions -> println(user.mountOptions.stringData)
// no need for an 'else' branch
}
}
fun main() {
val mountOne = MountOneOptions("foo", "bar", 111)
anotherCaller(User(mountOne))
val mountTwo = MountTwoOptions("foo", "bar", "mount two")
anotherCaller(User(mountTwo))
}
Output:
foo
bar
111
foo
bar
mount two
This is really the "default values" answer provided by Hubert Grzeskowiak adjusted to your example:
data class OneDetails(val c: Int)
data class TwoDetails(val c: String)
data class MountOptions(val a: String, val b: String)
data class User(
val mountOptions: MountOptions,
val detailsOne: OneDetails? = null,
val detailsTwo: TwoDetails? = null
)
fun main() {
fun anotherCaller(user: User) = println(user)
val mt = MountOptions("foo", "bar")
val one = OneDetails(1)
val two = TwoDetails("2")
val switch = "0"
when (switch) {
"0" -> anotherCaller(User(mt))
"1" -> anotherCaller(User(mt, detailsOne = one))
"2" -> anotherCaller(User(mt, detailsTwo = two))
"12" -> anotherCaller(User(mt, detailsOne = one, detailsTwo = two))
else -> throw IllegalArgumentException(switch)
}
}

Kotlin - get all properties from primary constructor

I have created this extension method which gets all properties from a KClass<T>
Extension Method
#Suppress("UNCHECKED_CAST")
inline fun <reified T : Any> KClass<T>.getProperties(): Iterable<KProperty1<T, *>> {
return members.filter { it is KProperty1<*, *> }.map { it as KProperty1<T, *> }
}
Example Usage
data class Foo(val bar: Int) {
val baz: String = String.EMPTY
var boo: String? = null
}
val properties = Foo::class.getProperties()
Result
val com.demo.Foo.bar: kotlin.Int
val com.demo.Foo.baz: kotlin.String
var com.demo.Foo.boo: kotlin.String?
How would I modify this extension method to only return properties that are declared in the primary constructor?
Expected Result
val com.demo.Foo.bar: kotlin.Int
You can take constructor parameters by getting primaryConstructor and then valueParameters,
and because primary constructor is not required for kotlin class we can do something like this
inline fun <reified T : Any> KClass<T>.getProperties(): Iterable<KParameter> {
return primaryConstructor?.valueParameters ?: emptyList()
}
so if we will ask for properties of Foo class
val properties = Foo::class.getProperties()
properties.forEach { println(it.toString()) }
we will get
parameter #0 bar of fun <init>(kotlin.Int): your.package.Foo
and the result is not a KProperty, but a KParameter which may be more aligned to your use case
val <T : Any> KClass<T>.constructorProperties
get() =
primaryConstructor?.let { ctor ->
declaredMemberProperties.filter { prop ->
ctor.parameters.any { param ->
param.name == prop.name
&&
param.type == prop.returnType
}
}
} ?: emptyList()
fun <T : Any> KClass<T>.getProperties(): Iterable<KProperty1<T, *>> =
constructorProperties
This is a rework of previous answers by szymon_prz and Peter Henry, to produce the list of properties declared in the primary constructor, but not:
other primary constructor parameters that are not properties
other properties that are not primary constructor parameters but have matching names and different types
Unfortunately it will still list properties that are not primary constructor parameters but have the same name and type as one of them.
For example:
// only parameter 'bar' is declared as a property
class Foo(val bar: Int, baz: Int, qux: Int, rod: Int) {
val zzz = baz // no parameter zzz
val qux = "##($qux)##" // property is a String but parameter is an Int
val rod = maxOf(0, rod) // property and parameter are both Int
}
val ctorProps = Foo::class.constructorProperties
ctorProps.forEach { println(it.toString()) }
will produce:
val Foo.bar: kotlin.Int
val Foo.rod: kotlin.Int
inline fun <reified T : Any> KClass<T>.getProperties(): List<KProperty<*>> {
val primaryConstructor = primaryConstructor ?: return emptyList()
// Get the primary constructor of the class ^
return declaredMemberProperties.filter {
// Get the declared properties of the class; i.e. bar, baz, boo
primaryConstructor.parameters.any { p -> it.name == p.name }
// Filter it so there are only class-properties whch are also found in the primary constructor.
}
}
To summarize, this function basically takes all the properties found in a class and filters them so only ones that are also found in the primary-constructor stay.

Kotlin DSL - union structure

I am designing a DSL and run into a requirement where I have a variable which could be assigned to different ways. Greatly simplified, I would like to set value property either by an integer or by an expression in String. (The real need is even more complex.)
I would like to write in my DSL:
value = 42
or
value = "6*7"
Behind the scene, the value will be stored in a DynamicValue<Int> structure which contains either an integer or the expression.
class DynamicValue<T>(dv : T?, expr : String) {
val directValue : T? = dv
val script : String? = expr
...
}
I tried several ways (delegate, class, etc), but none of them provided these syntax.
Is there a way to declare this union like structure?
What do you think about the following syntax:
value(42)
value("6*7")
//or
value+=42
value+="6*7"
You can do this with operator functions:
class DynamicValue<T>() {
var dv: T? = null
var expr: String? = null
operator fun invoke(dv : T) {
this.dv = dv
this.expr = null
}
operator fun invoke(expr: String) {
this.dv = null
this.expr = expr
}
operator fun plusAssign(dv : T) {
this.dv = dv
this.expr = null
}
operator fun plusAssign(expr: String) {
this.dv = null
this.expr = expr
}
}
You can't redefine the assign operator in Kotlin, therefor the pure syntax value=42 is not possible.
But I wouldn't go with operator functions, it's to magical. I would do this:
val value = DynamicValue<Int>()
value.simple=42
value.expr="6*7"
class DynamicValue2<T>() {
private var _dv: T? = null
private var _expr: String? = null
var simple: T?
get() = _dv
set(value) {
_dv = value
_expr = null
}
var expr: String?
get() = _expr
set(value) {
_expr = value
_dv = null
}
}
Rene's answer gave me the lead and finally I turned up with this solution.
In this solution I took all my requirements in (the ones I dropped out in my original question) so this became much more complicated than my original question would have required.
My whole requirement was to be able to add static values or scripts (snippets) running on a well guarded context. These script would be stored, and executed later. I wanted to enable the whole power of the IDE when writing the script, but would like to guard my scripts from code injections and help the user to use only the context values the script requires.
The trick I used to achieve this is to enable adding script in kotlin, but before I run the whole DSL script and create the business objects, I convert the script into a string. (This string will be executed later in a guarded, wrapped context by JSR233 engine.) This conversation forced me to tokenize the whole script before execution and search/replace some of the tokens. (The whole tokenizer and converter is rather long and boring, so I won't insert here.)
First approach
What my goal was to be able to write any of this:
myobject {
value = static { 42 } // A static solution
value = static { 6 * 7 } // Even this is possible
value = dynamic{ calc(x, y) } // A pure cotlin solution with IDE support
value = dynamic("""calc(x * x)""") // This is the form I convert the above script to
}
where calc, x and y are defined in the context class:
class SpecialScriptContext : ScriptContextBase() {
val hello = "Hello"
val x = 29
val y = 13
fun calc(x: Int, y: Int) = x + y
fun greet(name: String) = println("$hello $name!")
}
So let's see the solution! First I need a DynamicValue class to hold one of the values:
class DynamicValue<T, C : ScriptContextBase, D: ScriptContextDescriptor<C>>
private constructor(val directValue: T?, val script: String?) {
constructor(value: T?) : this(value, null)
constructor(script: String) : this(null, script)
}
This structure will ensure that exactly one of the options (static, script) will be set. (Don't bother with the C and D type parameters, they are for context-based script support.)
Then I made top level DSL functions to support syntax:
#PlsDsl
fun <T, C : ScriptContextBase, D : ScriptContextDescriptor<C>> static(block: () -> T): DynamicValue<T, C, D>
= DynamicValue<T, C, D>(value = block.invoke())
#PlsDsl
fun <T, C : ScriptContextBase, D : ScriptContextDescriptor<C>> dynamic(s: String): DynamicValue<T, C, D>
= DynamicValue<T, C, D>(script = s)
#PlsDsl
fun <T, C : ScriptContextBase, D : ScriptContextDescriptor<C>> dynamic(block: C.() -> T): DynamicValue<T, C, D> {
throw IllegalStateException("Can't use this format")
}
An explanation to the third form. As I wrote before, I don't want to execute the block of the function. When the script is executed, this form is converted to the string form, so normally this function would never appear in the script when executed. The exception is a sanity warning, which would never be thrown.
Finally added the field to my business object builder:
#PlsDsl
class MyObjectBuilder {
var value: DynamicValue<Int, SpecialScriptContext, SpecialScriptContextDescriptor>? = null
}
Second approach
The previous solution worked but had some flaws: the expression was not associated with the variable it set, neither with the entity the value was set in. With my second approach I solved this problem and removed the need of equal sign and most of the unnecessary curly brackets.
What helped: extension functions, infix functions and sealed classes.
First, I split the two value types into separated classes defined a common ancestor:
sealed class Value<T, C : ScriptContextBase> {
abstract val scriptExecutor: ScriptExecutor
abstract val descriptor: ScriptContextDescriptor<C>
abstract val code: String
abstract fun get(context: C): T?
}
class StaticValue<T, C : ScriptContextBase>(override val code: String,
override val scriptExecutor: ScriptExecutor,
override val descriptor: ScriptContextDescriptor<C>,
val value: T? = null
) : Value<T, C>() {
override fun get(context: C) = value
constructor(oldValue: Value<T, C>, value: T?) : this(oldValue.code, oldValue.scriptExecutor, oldValue.descriptor, value)
}
class DynamicValue<T, C : ScriptContextBase>(override val code: String,
script: String,
override val scriptExecutor: ScriptExecutor,
override val descriptor: ScriptContextDescriptor<C>)
: Value<T, C>() {
constructor(oldValue: Value<T, C>, script: String) : this(oldValue.code, script, oldValue.scriptExecutor, oldValue.descriptor)
private val scriptCache = scriptExecutor.register(descriptor)
val source = script?.replace("\\\"\\\"\\\"", "\"\"\"")
private val compiledScript = scriptCache.register(generateUniqueId(code), source)
override fun get(context: C): T? = compiledScript.execute<T?>(context)
}
Note, that I made the primary constructor internal and created a kind of copy and alter constructor. Then I defined the new functions as extension of the common ancestor and marked them infix:
infix fun <T, C : ScriptContextBase> Value<T, C>.static(value: T?): Value<T, C> = StaticValue(this, value)
infix fun <T, C : ScriptContextBase> Value<T, C>.expr(script: String): Value<T, C> = DynamicValue(this, script)
infix fun <T, C : ScriptContextBase> Value<T, C>.dynamic(block: C.() -> T): Value<T, C> {
throw IllegalStateException("Can't use this format")
}
Using the secondary copy-and-alter constructor allows to inherit the context sensitive values. Finally I initialize the value inside the DSL builder:
#PlsDsl
class MyDslBuilder {
var value: Value<Int, SpecialScriptContext> = StaticValue("pl.value", scriptExecutor, SpecialScriptContextDescriptor)
var value2: Value<Int, SpecialScriptContext> = StaticValue("pl.value2", scriptExecutor, SpecialScriptContextDescriptor)
}
Everything is in place and now I can use it in my script:
myobject {
value static 42
value2 expr "6 * 7"
value2 dynamic { calc(x, y) }
}

Kotlin: Function-param implementation in a body of a caller

fun lazyProperty(initializer: () -> Int): Int {
val result: Lazy<Int> = lazy(initializer)
return result.value
}
fun main(args: Array<String>) {
// 1.
val bar: Int = lazyProperty({ 1 + 1 })
// 2.
val foo: Int = lazyProperty() {
42
}
println("bar $bar, foo: $foo")
}
I recently stumbled over the syntax of calling a function in Kotlin and I just don't get it:
the fist option is clear - it's a lambda, but the second one looks not like a usual syntax of calling a function with the required parameter. The brackets where normally params should be placed are empty and instead the function-parameter comes in the body of the caller! How is it possible and what for is it needed?
This is another valid way of passing a lambda. According to the docs:
In Kotlin, there is a convention that if the last parameter to a function is a function, and you're passing a lambda expression as the corresponding argument, you can specify it outside of parentheses:
lock (lock) {
sharedResource.operation()
}
You can choose whichever approach you prefer.
This is just convention. If the last param of a function is a function, you can pass the lambda outside the parentheses. In your case you have the following options:
val bar: Int = lazyProperty({ 1 + 1 })
val bar: Int = lazyProperty() { 1 + 1 }
val bar: Int = lazyProperty { 1 + 1 }
All three options are the same.
If your function would have a second parameter (at first position), than the calls could look like this:
fun lazyProperty(x: Int, initializer: () -> Int): Int {...}
val bar: Int = lazyProperty(7, { 1 + 1 })
val bar: Int = lazyProperty(7) { 1 + 1 }
If your function would have a second parameter (at second position), than the calls could look like this:
fun lazyProperty(initializer: () -> Int, x: Int): Int {...}
val bar: Int = lazyProperty({ 1 + 1 }, 7)
So always try to keep the Lambda at the last position of your function.

How to propagate default arguments between functions in Kotlin?

Kotlin has default arguments for function and constructor parameters. Now, I have a function
fun foo(bar: String = "ABC", baz: Int = 42) {}
and I want to call it from different places but also retain the possibility to not pass on of the arguments and instead use the default value.
I know, I can declare the default arguments in the calling functions
fun foo2(bar: String = "ABC", baz: Int = 42) {
// do stuff
foo(bar, baz)
}
fun foo3(bar: String = "ABC", baz: Int = 42) {
// do other stuff
foo(bar, baz)
}
but now my default parameter in foo is pointless as it's always overwriten and I have duplicated the default arguments in all calling functions. That's not very DRY.
Is there a better way to propagate the default arguments?
Instead of having three functions with the same default arguments:
fun foo(bar: String = "ABC", baz: Int = 42)
fun foo2(bar: String = "ABC", baz: Int = 42)
fun foo3(bar: String = "ABC", baz: Int = 42)
Create a wrapper class that takes in the arguments, and have the functions without parameters:
class Foo(val bar: String = "ABC", val baz: Int = 42) {
fun foo() { /* ... */ }
fun foo2() {
// ...
foo()
}
fun foo3() {
// ...
foo()
}
}
Answering my own question as encouraged by the guidelines.
What you can do, is declare the parameters in the calling functions as nullable and use null as the default argument:
fun foo2(bar: String? = null: Int? = null) {
// do stuff
foo(bar, baz)
}
fun foo3(bar: String? = null, baz: Int? = null) {
// do other stuff
foo(bar, baz)
}
Then, use one of the elvis operator to use default values, when null is provided.
fun foo(bar: String? = null, baz: Int? = null) {
val realBar = bar ?: "ABC"
val realBaz = baz ?: 42
}
If you're dealing with a class instead of a function, you can pull out the property of the constructor and assign the default value there:
class Foo(bar: String? = null, baz: Int? = null) {
val bar = bar ?: "ABC"
val baz = baz ?: 42
}
Alternatively, say if your class is a data class and you want to have the properties in the primary constructor, you can declare a factory method to handle the default values:
class Foo(val bar: String, baz: Int) {
companion object {
fun create(bar: String? = null, baz: Int? = null) = Foo(bar ?: "ABC", baz ?: 42)
}
}
If you have multiple functions taking the same arguments (with the same default values), then that is a code-smell suggesting that those parameters are closely related and should live inside their own class.
data class FooArgs( val bar: String = "ABC", val baz: Int = 42 )
fun foo( args: FooArgs = FooArgs() ) { /* ... */}
fun foo2( args: FooArgs = FooArgs() ) {
...
foo(args)
}
fun foo3( args: FooArgs = FooArgs() ) {
...
foo(args)
}
If foo and/or foo2, foo3 only use their arguments, then further refactoring would move foo and foo2 into the FooArgs class, resulting in a similar solution to the answer by #nhaarman