I have the following class hierarchy.
sealed trait Foo {
val a: String
}
case class Bar1(a: String) extends Foo
case class Bar2(a: String) extends Foo
Now I want to add a convenient method to modify the field a. I need this method to be accessible in the super type Foo and I want to use the .copy method of the case class (because I actually have a lot more fields and it is painful to use the constructor). My first attempt was to use pattern matching :
sealed trait Foo {
val a: String
def withField(b: String) = this match {
case b1: Bar1 => b1.copy(a = b)
case b2: Bar2 => b2.copy(a = b)
}
}
Now I would also like my withField method to return an instance type of the caller, B1 if the method is called by an instance of type B1, B2 if the method is called by an instance of type B2 and Foo if this is all I know. So I thought to myself I might be able to parametrized the method withFieldto serve this purpose. Something like:
sealed trait Foo {
val a: String
def withField[A <: Foo](b: String) = this match {
case b1: Bar1 => b1.copy(a = b)
case b2: Bar2 => b2.copy(a = b)
}
}
but I don't manage to parametried withField with the type of this.
Am I getting completely wrong here ? Should I use a different pattern maybe using override modifier ?
Thanks a lot
Am I getting completely wrong here ? Should I use a different pattern maybe using override modifier ?
Yes. There are two alternatives:
sealed trait Foo {
val a: String
def withField(b: String): Foo
}
case class Bar1(a: String) extends Foo {
// return types are covariant, and Bar1 is subtype of Foo,
// so this is legal
def withField(b: String): Bar1 = ...
}
or
sealed trait Foo[ThisType <: Foo[ThisType]] {
val a: String
def withField(b: String): ThisType
}
case class Bar1(a: String) extends Foo[Bar1] {
def withField(b: String): Bar1 = ...
}
Note the second is more complex, and should only be used if you actually need it.
EDIT to answer Christian's question:
sealed trait Foo {
type ThisType <: Foo
def withField(b: String): ThisType = (this match {
case b1: Bar1 => b1.copy(a = b)
...
}).asInstanceOf[ThisType]
}
case class Bar1(a: String) extends Foo {
type ThisType = Bar1
}
I don't like it: it needs a cast, actually using it would require dependent method types, and I wouldn't be surprised if it broke down in practice (e.g. because the compiler can't prove foo.ThisType and foo.withField("a").ThisType are the same).
Related
Let's say I have a class Foo
class Foo {
val noProblem = "Hakuna Matata"
}
I now want to decorate it with an ID. Kotlin's delegates make this relatively painless:
class IdentifiableFoo(
val id: Int,
foo: Foo,
): Foo by foo
interface Foo {
val noProblem: String
}
class FooImpl: Foo {
override val noProblem = "Hakuna Matata"
}
but let's say I have another class Bar that I also want to decorate, and then another Baz, and then another ...
I could just create a IdentifiableXYZ for each of them, of course.
But what I really want is something akin to
class Identifiable<T> (
val id: Int,
thing: T
): T by thing
That I could just use for all of them.
And yes, there's a very good chance that the language doesn't support something like that, but the error message made me think:
Only classes and interfaces may serve as supertypes
so can I do some where magic or something to tell Kotlin that T is required to be an interface?
It is not possible in Kotlin. Kotlin only allows super type to be either a class or an interface. Type parameter cannot be a super type of some class or interface.
One workaround might be using a base interface for all the interfaces you are using. But not sure it solves your problem.
class IdentifiableFoo<T : BaseFoo>(
val id: Int,
val foo: T,
) : BaseFoo by foo {
fun doSomething(a: T.() -> String) {
println(a.invoke(foo))
}
}
interface Foo0 : BaseFoo {
val noProblem: String
}
interface Foo1 : BaseFoo {
val someProblem: String
}
class FooImpl : Foo0 {
override val noProblem = "Hakuna Matata"
}
class Foo1Impl() : Foo1 {
override val someProblem: String = "Some Problem"
}
interface BaseFoo {}
Usage:
IdentifiableFoo<Foo0>(2, FooImpl()).doSomething {
this.noProblem
} // Prints "Hakuna Matata"
IdentifiableFoo<Foo1>(2, Foo1Impl()).doSomething {
this.someProblem
} // Prints "Some Problem"
Playground link
I wanted to be able to define a method to clone an object that is the same type of itself. I define the interface requesting such, but the following does not compile or run.
interface Foo {
fun <T: Foo> copy() : T
}
class Bar(private val v:Int) : Foo {
override fun copy():Bar = Bar(v)
}
main() {
val bar1 = Bar(1)
val bar2 = bar1.copy()
}
If however I write the implementing class in Java, it will compile
class Bar implements Foo {
private int v;
public Bar(int v) {this.v = v;}
public Bar copy() {
return new Bar(v);
}
}
I can rewrite the code like the following that compiles:
interface Foo<out Foo>{
fun copy(): Foo
}
class Bar(private val v:Int) : Foo<Bar> {
override fun copy(): Bar = Bar(v)
}
However the following will fail with error: no type arguments expected for fun copy(): Foo
val newF = f.copy()
fun <T: Foo> addFoo(
foo: T,
fooList: List<T>,
): MutableList<T> {
val result: MutableList<T> = arrayListOf()
for (f in fooList) {
val newF = f.copy<T>()
result.add(newF)
}
result.add(foo)
return result
}
Is there a good solution to the problem?
The problem here is that Foo doesn't know the exact type of the implementing class, so has no way to specify that its method returns that same type.
Unfortunately, Kotlin doesn't have self types (see this discussion), as they would handle this situation perfectly.
However, you can get close enough by using what C++ calls the curiously-recurring template pattern. In Kotlin (and Java) you do this by defining Foo with a type parameter explicitly extending itself (including its own type parameter):
interface Foo<T : Foo<T>> {
fun copy(): T
}
Then the implementing class can specify itself as the type argument:
class Bar(private val v: Int) : Foo<Bar> {
override fun copy(): Bar = Bar(v)
}
And because T is now the correct type, everything else works out. (In fact, the : Bar is redundant there, because it already knows what the type must be.)
Your addFoo() method will then compile with only a couple of changes: give it the same type parameter <T: Foo<T>>, and remove the (now wrong, but unnecessary) type parameter when calling f.copy(). A quick test suggests it does exactly what you want (creates a list with clones of fooList followed by foo).
Since it's often useful for a superclass or interface to refer to the implementing class, this pattern crops up quite often.
BTW, your code is easier to test if Bar has its own toString() implementation, as you can then simply print the returned list. You could make it a data class, or you could write your own, e.g.:
override fun toString() = "Bar($v)"
I am writing a class that contains two immutable values, which are set in the primary constructor. I would like to add a secondary constructor that takes a string and parses it to get those two values. However, I can't figure out a way to implement this in Kotlin, as the secondary constructor calls the primary constructor immediately, before parsing the string.
In java, I would call this(a,b) in one of the other constructors, but Java doesn't have primary constructors. How do I add this functionality?
class Object (a: double, b:double)
{
val a = a
val b = b
constructor(str: String) //Parsing constructor
{
//Do parsing
a = parsed_a
b = parsed_b
}
}
You can either replace your parsing constructor with a factory method:
class Object(val a: Double, val b: Double) {
companion object {
// this method invocation looks like constructor invocation
operator fun invoke(str: String): Object {
// do parsing
return Object(parsed_a, parsed_b)
}
}
}
Or make both constructors secondary:
class Object {
val a: Double
val b: Double
constructor(a: Double, b: Double) {
this.a = a
this.b = b
}
// parsing constructor
constructor(str: String) {
// do parsing
a = parsed_a
b = parsed_b
}
}
Secondary constructors are disfavored in Kotlin. Your best solution is to use a factory method. See, e.g.:
class A(val a: Int, val b: Int) {
companion object {
fun fromString(str: String): A {
val (foo, bar) = Pair(1, 2) // sub with your parsing stuff
return A(foo, bar)
}
}
}
This will lead to more readable code. Imagine a class with ten different constructors identified no way other than MyClass as opposed to many more obvious ones enabled by the factory approach: MyClass.fromString(str: String) and MyClass.fromCoordinates(coordinates: Pair<Int, Int>) and so forth.
Secondary constructors weren't even allowed in Kotlin until relatively recently.
I want to add some behavior to the T generic type of my class, but I still want my class to exist. I want a class to be both itself and the T type. For instance:
open class Foo(val bar: String)
class Bar(val baz: String) : Foo("bar") {
}
This is an easy case, because I know Foo type in advance. If I make Foo an interface, I can delegate it's methods to a parameter:
interface Foo {
fun bar() = "bar"
}
class Bar(val foo: Foo) : Foo by foo {
}
And Bar is still a Foo.
But what if I don't know what type it is at this time? I want Bar to be T aswell as Bar, and I thought of something like this:
class Bar<T>(val t: T) : T by t
But it doesn't compile. I wanted to use it in this fashion:
fun doWithFoo(s: String) {
print(s)
}
fun unknownFoo() {
val bar = Bar("baz")
doWithFoo(bar)
}
This might be a bizarre use case, but it's necessary. I need it to capture arguments passed to a function, and make assertions over it, but I need the function to still be valid, so:
fun foo(b: Bar){
print(b.toString())
}
If I want to create an argument capturer for the function foo, I could create something that captures it
class Capturer<T>(t: T) {
//code that captures the value and make assertions over it
}
But then the function would become invalid:
val capturer = Capturer<Bar>(Bar("X"))
foo(capturer) //Invalid
So I need capturer to also be a Bar. This way the function foo is still valid.
How could I make the Bar class to be both a Bar and the generic type T?
I don't think that I really got what you are trying to accomplish.
But maybe the following may help you solve your problem?
If you use
fun <T> foo(b: T) {
//...
and
fun <T> doWithFoo(s: T) {
//...
at least the call will not be invalid anymore.
In C# you can place a new constraint on a generic to create a new instance of the generic parameter type, is there an equivalent in Kotlin?
Right now my work around is this:
fun <T> someMethod(class : () -> T) {
val newInstance = class()
}
and I'm calling someMethod() like this
someMethod<MyClass>(::MyClass)
but I would like to do something like this:
fun <T : new> someMethod() {
val newInstance = T()
}
Is that possible?
Currently, that's not possible. You can give a thumbs-up for the issue https://youtrack.jetbrains.com/issue/KT-6728 to vote for the addition of this feature.
At least, you can leave out the generic type because Kotlin can infer it:
someMethod(::MyClass)
A solution:
1/ use an inline function with preserved param type (reified type)
2/ in this inline function, invoque the needed constructor using class introspection (reflexion *)
/!\ an inline function can't be nested/embedded in a class or function
Let see how it works on a simple example:
// Here's 2 classes that take one init with one parameter named "param" of type String
//!\ to not put in a class or function
class A(val param: String) {}
class B(val param: String) {}
// Here's the inline function.
// It returns an optional because it could be passed some types that do not own
// a constructor with a param named param of type String
inline fun <reified T> createAnInstance(value: String) : T? {
val paramType = String::class.createType() //<< get createAnInstance param 'value' type
val constructor = T::class.constructors.filter {
it.parameters.size == 1 && it.parameters.filter { //< filter constructors with 1 param
it.name == "param" && it.type == paramType //< filter constructors whose name is "param" && type is 'value' type
}.size != 0
}.firstOrNull() //< get first item or returned list or null
return constructor?.call(value) // instantiate the class with value
}
// Execute. Note that to path the type to the function val/var must be type specified.
val a: A? = createAnInstance("Wow! A new instance of A")
val b: B? = createAnInstance("Wow! A new instance of B")
*) kotlin-reflect.jar must be included in the project
In Android Studio: add to build.gradle(Module: app): implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"