I have a class that has the type parameter KFunction<*>
(It is understood that I will indicate the type of function that I want to work with in the future)
I want a class method to take the same parameters that KFunction has and call subscribers with those arguments. However, I don't know how to get the type of the function parameters. There are delegates in C #. How to do something like this in Kotlin?
My class:
class Event<Function: KFunction<*>> {
val subscribers = mutableListOf<Function>()
operator fun plus(increment: Function) {
subscribers.add(increment)
}
// i want arguments as KFunction
operator fun invoke(args: ???) {
subscribers.forEach { it(args) }
}
}
fun c(i: Int) {
println("result of function c: $i")
}
fun main() {
val event = Event<KFunction1<Int, Unit>>()
event + ::c
event(100) // passing arguments Int from KFunction1<Int, Unit>
}
Is there a way to implement my idea exactly like this?
So, it's implied that type, passed as a Function : KFunction<*> type parameter (KFunction1<Int, Unit> in this case) will have its own type parameters (Int and Unit in this case), and you want to declare args parameter as an uncertain amount of parameters with these exact types (excluding the last one, which represents type of function call result)?
I believe it's impossible.
The best you can do is to declare args as vararg with Any? type:
operator fun invoke(vararg args: Any?) = subscribers.forEach { it.call(*args) }
It seems that the only normal solution to problem is to accept only 1 abstract type of parameters.
class Event<ArgsT> {
private val subscribers = mutableListOf<(ArgsT) -> Any>()
operator fun plusAssign(increment: (ArgsT) -> Any) {
subscribers.add(increment)
}
operator fun invoke(params: ArgsT) {
subscribers.forEach { it(params) }
}
}
fun c(i: Int, b: Int) {
println(i + b)
}
data class Data(val i: Int, val b: Int)
fun main() {
val event = Event<Data>()
event += { (i, b) -> c(i, b) } // pass arguments using a lambda
event(Data(2, 5))
}
Related
I am trying to write an Iterator for this generic class:
class Point<T>(val x: T, val y: T): Comparable<Point<T>> where T: Number, T: Comparable<T> {
override fun compareTo(other: Point<T>): Int {
return compareValuesBy(this, other, Point<T>::x, Point<T>::y
)
}
}
I can create a range of Points: val range = Point(1,1) .. Point(10.10). When I call forEach on range, it says something to the effect that I have to write an iterator. So here's what I've tried:
operator fun ClosedRange<Point<T>>.iterator(): Iterator<Point<T>>{}
The error is 'unresolved reference T'. What should I do here?
UPDATE:
Now the working iterator function is this:
operator fun <T> ClosedRange<Point<T>>.iterator(): Iterator<T>
where T: Number, T: Comparable<T>
I've hit another road block. I need to increase the Point so that the overriden next function can work. So I have this extension inc function:
operator fun <T> Point<T>.inc(): Point<T> where T: Number, T: Comparable<T> {
return this(x + 1, y +1)
}
The problem is 'this(x + 1, y + 1) doesn't work because x and y are type T. How can you work around this?
Your extension function is for a generic type, thus type parameters need to be declared:
class Point<T>(val x: T, val y: T): Comparable<Point<T>> where T: Number, T: Comparable<T> {
override fun compareTo(other: Point<T>): Int {
return compareValuesBy(this, other, Point<T>::x, Point<T>::y
)
}
}
operator fun <T> ClosedRange<Point<T>>.iterator(): Iterator<Point<T>> where T : Number, T: Comparable<T> {
......
}
The following code does the same thing. The functions tr and td take a function literal with receiver object as input in order to add tr or td tag inside of a table.
class TABLE : Tag("table") {
fun tr(init: TR.() -> Unit) {
children += TR().apply(init)
}
}
class TR : Tag("tr") {
fun td(init: TD.() -> Unit) {
val td = TD()
td.init()
children += td
}
}
My Question is why do I need to use .apply() instead of:
class TABLE : Tag("table") {
fun tr(init: TR.() -> Unit) {
children += TR().init()
}
}
I guess it has something to do with the compiler looking for init() in the tr-object. But shouldn't this be decided on runtime?
As already suggested in my comment, using .apply you can chain invocations of init and += together, because apply returns the target of its invocation.
If you prefer to use init(), you can obtain the same result with
val tr = TR()
children += tr
tr.init()
The key aspect of the chained variation is that the applyfunction of the Kotlin's standard library is defined as an extension function of a generic typeT, accepting a *lambda with receiver as its sole parameter, as you can see here:
inline fun <T> T.apply(block: T.() -> Unit): T
In order to explain its meaning, you can implement this function yourself:
fun <T> T.myApply(block: T.() -> Unit) : T {
this.block()
return this
}
The following example mimics your code, using a fake MyClass type in place of the original TR:
fun <T> T.myApply(block: T.() -> Unit) : T {
this.block()
return this
}
class MyClass(val text: String) {
fun foo() : Unit {
println("foo $text")
}
}
fun initializer(mc: MyClass) {
println("initializer ${mc.text}")
mc.foo()
}
fun run(init: MyClass.() -> Unit) {
val result = MyClass("first").myApply(init)
val x = MyClass("second")
x.init()
}
fun main(args: Array<String>) {
run(::initializer)
}
You can play with this example in order to follow the flow from run to MyClass.foo, through the function accepting init as lambda with receiver parameter: I hope this can help you to clarify your understanding of the key charateristics of both the original and the alternative implementation of tr.
There is normal extension function
fun <T> List<T>.test() { }
fun String.test(){ }
and i can declare a variable with extension function type
val obj1 = fun String.(){ }
but can not declare a variable with generic extension function type
val obj2 = fun <T> List<T>.() {} //error
P.S. I don't want to use List<Any>, it is different from generic.
Can someone tell me how to solve it? Thanks!!
Extension functions are kind of a red herring. Values can't have polymorphic types at all, not just in Kotlin, but in Java, Scala, etc.
Depending on why you want it, one approach would be a generic function returning an extension function:
fun <T> obj2Generic() = fun List<T>.() {}
val obj2ForInt = obj2Generic<Int>()
val obj2ForString = obj2Generic<String>()
You cannot do it, same as you cannot assign regular (non-extension) generic function to a variable or instantiate a generic class without specifying the type parameter. Try to put yourself in the compiler's shoes: what would be the type of such variable? It would be generic as well, so it is only possible in another generic context.
fun <T> List<T>.test(){ }
val v1 = List::test // No
val v2 = List<String>::test // OK
fun <T> other() {
val v3 = List<T>::test // OK
val v4 = fun List<T>.() { } // OK
}
Let's say I have a data class like this:
data class MyData(val something: Int, val somethingElse : String) {
init {
require(something > 20) { "Something must be > 20" }
require(StringUtils.isNotEmtpy(somethingElse)) { "Something else cannot be blank" }
}
}
I'd like to be able to apply a function to somethingElse before the init method is called. In this case I want to remove all \n characters from the somethingElse String while maintaining immutability of the field (i.e. somethingElse must still be a val). I'd like to do something similar to this in Java:
public class MyData {
private final int something;
private final String somethingElse;
public MyDate(int something, String somethingElse) {
this.something = something;
this.somethingElse = StringUtils.replace(somethingElse, '\n', '');
Validate.isTrue(something > 20, "...");
Validate.isTrue(StringUtils.isNotEmtpy(this.somethingElse), "...");
}
// Getters
}
I could of course create a normal class (i.e. no data class) in Kotlin but I want MyData to be a data class.
What is the idiomatic way to do this in Kotlin?
While you can not literally do what you want, you can fake it.
Make all constructors of your data class private.
Implement factories/builders/whatevers on the companion as operator fun invoke.
Usages of Companion.invoke will -- in Kotlin! -- look just like constructor calls.
In your example:
data class MyData private constructor(
val something: Int,
val somethingElse : String
) {
init {
require(something > 20) { "Something must be > 20" }
require("" != somethingElse) { "Something else cannot be blank" }
}
companion object {
operator fun invoke(something: Int, somethingElse: String) : MyData =
MyData(something, somethingElse.replace("\n", " "))
}
}
fun main(args: Array<String>) {
val m = MyData(77, "something\nwicked\nthis\nway\ncomes")
println(m.somethingElse)
}
Prints:
something wicked this way comes
You'll note the helpful warning:
Private data class constructor is exposed via the generated 'copy' method.
This method can not be overridden (as far as I can tell) so you have to take care, still. One solution is to hide the actual data class away:
interface MyData {
val s: Int
val sE: String
private data class MyDataImpl(
override val s: Int,
override val sE: String
) : MyData {
init {
require(s > 20) { "Something must be > 20" }
require("" != sE) { "Something else cannot be blank" }
}
}
companion object {
operator fun invoke(s: Int, sE: String) : MyData =
MyDataI(s, sE.replace("\n", " "))
}
}
Now your invariant (no line breaks) is maintained, copy and other dangerous methods (if any, I haven't checked) are hidden away -- but therefore also unavailable, potentially removing some of the convenience data classes provide.
Choose your poison.
I am new to Kotlin and working through the tutorials that are available.
However now I seem to have a problem with a secondary constructor:
Parameters declared in the primary constructor can be accessed in a function,
but when I try to do this with a parameter from the secondary constructor I get an error: Unresolved reference:nbr
The code:
class Test(_name: String) {
val name: String = _name
constructor(_name: String, _nbr: Int) : this(_name) {
val nbr: Int = _nbr
}
fun printNameAndNumber() {
println("Name: $name")
println("Number: $nbr")
}
}
It is clear to me that I am doing something basically wrong but who can tell me what?
nbr should be a variable, because in this specific case it is optional:
class Test(_name: String) {
val name: String = _name
var nbr: Int? = null
constructor(_name: String, _nbr: Int) : this(_name) {
this.nbr = _nbr
}
fun printNameAndNumber() {
println("Name: $name")
println("Number: $nbr")
}
}
Parameters of the primary constructor are not available in member functions. Fields are. Fortunately Kotlin has a short syntax to make primary constructor parameters member properties right away.
What do you expect nbr to be when constructed using the primary constructor? I suggest you to swap your constructors, so it's clear what are properties and what are just parameters:
class Test(val name: String, val nbr: Int) {
constructor(name: String) : this(name, 0)
fun printNameAndNumber() {
println("Name: $name")
println("Number: $nbr")
}
}
fun main(args : Array<String>) {
Test("Péter").printNameAndNumber()
}
name is accessible because it is a member.
nbr is not accessible because it is a local (immutable) variable inside the secondary constructor.
If you declare nbr as member: putting val nbr: Int for example below the val name line, it will be accessible, however it will not compile if nbr is defined as immutable (val).
A simpler structure would be:
class Test(_name: String, _nbr: Int = 0) {
val name: String = _name
val nbr: Int = _nbr
fun printNameAndNumber() {
println("Name: $name")
println("Number: $nbr")
}
}
or even simpler
class Test(val name: String, val nbr: Int = 0) {
fun printNameAndNumber() {
println("Name: $name")
println("Number: $nbr")
}
}
If you want your nbr member as nullable, you couldgo with the suggestion of #gil.fernandes.