My goal is to make a rename
method that returns the class type itself. But this rename
is an interface to implement defined in the the base class so that the base class can use it.
void main() {
Cat cat = Cat("Whiskers");
Dog dog = Dog("Buddy");
Cat renamedCat = cat.rename("Mittens");
Dog renamedDog = dog.rename("Max");
print("Old cat name: ${cat.name}, New cat name: ${renamedCat.name}");
print("Old dog name: ${dog.name}, New dog name: ${renamedDog.name}");
}
Both of these two versions don't work.
Version 1:
abstract class Animal {
final String name;
Animal(this.name);
T copyWith<T extends Animal>({String? name});
}
class Cat extends Animal {
Cat(super.name);
@override
Cat copyWith({String? name}) {
return Cat(name ?? this.name);
}
}
class Dog extends Animal {
Dog(super.name);
@override
Dog copyWith({String? name}) {
return Dog(name ?? this.name);
}
}
extension AnimalExtension<T extends Animal> on T {
T rename(String newName) {
return copyWith(name: newName);
}
}
Gives this error:
Cat.copyWith' ('Cat Function({String? name})') isn't a valid override of 'Animal.copyWith' ('T Function<T extends Animal>({String? name})').
Version 2:
abstract class Animal {
final String name;
Animal(this.name);
Animal copyWith({String? name});
}
class Cat extends Animal {
Cat(super.name);
@override
Cat copyWith({String? name}) {
return Cat(name ?? this.name);
}
}
class Dog extends Animal {
Dog(super.name);
@override
Dog copyWith({String? name}) {
return Dog(name ?? this.name);
}
}
extension AnimalExtension<T extends Animal> on T {
T rename(String newName) {
return copyWith(name: newName);
}
}
Gives the error:
A value of type 'Animal' can't be returned from the method 'rename' because it has a return type of 'T
There is a solution that works, but I don't like, it smells so much, because it is bad programming:
return copyWith(name: newName) as T; // this just forces it to work
My goal is to make a rename
method that returns the class type itself. But this rename
is an interface to implement defined in the the base class so that the base class can use it.
void main() {
Cat cat = Cat("Whiskers");
Dog dog = Dog("Buddy");
Cat renamedCat = cat.rename("Mittens");
Dog renamedDog = dog.rename("Max");
print("Old cat name: ${cat.name}, New cat name: ${renamedCat.name}");
print("Old dog name: ${dog.name}, New dog name: ${renamedDog.name}");
}
Both of these two versions don't work.
Version 1:
abstract class Animal {
final String name;
Animal(this.name);
T copyWith<T extends Animal>({String? name});
}
class Cat extends Animal {
Cat(super.name);
@override
Cat copyWith({String? name}) {
return Cat(name ?? this.name);
}
}
class Dog extends Animal {
Dog(super.name);
@override
Dog copyWith({String? name}) {
return Dog(name ?? this.name);
}
}
extension AnimalExtension<T extends Animal> on T {
T rename(String newName) {
return copyWith(name: newName);
}
}
Gives this error:
Cat.copyWith' ('Cat Function({String? name})') isn't a valid override of 'Animal.copyWith' ('T Function<T extends Animal>({String? name})').
Version 2:
abstract class Animal {
final String name;
Animal(this.name);
Animal copyWith({String? name});
}
class Cat extends Animal {
Cat(super.name);
@override
Cat copyWith({String? name}) {
return Cat(name ?? this.name);
}
}
class Dog extends Animal {
Dog(super.name);
@override
Dog copyWith({String? name}) {
return Dog(name ?? this.name);
}
}
extension AnimalExtension<T extends Animal> on T {
T rename(String newName) {
return copyWith(name: newName);
}
}
Gives the error:
A value of type 'Animal' can't be returned from the method 'rename' because it has a return type of 'T
There is a solution that works, but I don't like, it smells so much, because it is bad programming:
return copyWith(name: newName) as T; // this just forces it to work
Share
Improve this question
edited Nov 19, 2024 at 16:30
TSR
asked Nov 19, 2024 at 16:06
TSRTSR
20.7k31 gold badges119 silver badges237 bronze badges
1 Answer
Reset to default 0The problem is that not parsing as T is already bad programming
extension AnimalExtension<T extends Animal> on T {
T rename(String newName) {
/// T can be anything as long as its an animal,
/// you can: return Dog(newName)
/// and it will still be a valid return because dog is of type T, even if you call it from a Cat perspective
return copyWith(name: newName);
}
}
T extends Animal means that whatever it is it can transform from <E extends Animal>
to <R extends Animal>
as long as the supertype Animal is present in both (Cat and Dog for example), so as T
purpose is for the program to understands that whatever copyWith does it returns the same type that the input (if rename was calling on a Dog, the output as to be of a Dog)
the 2 best options are:
- make rename a method and override it
- make an extension of each type
This example shows that you can have an extension for type and also that if you have an extension for the parent class Animal and the one calling it is an Animal (not a specific type, but the parent) you can call it without risk:
abstract class Animal {
final String name;
Animal(this.name);
Animal copyWith({String? name}); /// no need for T, this is not a generic class, all copyWith are already a type of the same class Animal
}
class Cat extends Animal {
Cat(super.name);
@override
Cat copyWith({String? name}) {
return Cat(name ?? this.name);
}
}
class Dog extends Animal {
Dog(super.name);
@override
Dog copyWith({String? name}) {
return Dog(name ?? this.name);
}
}
extension CatExtension on Cat {
Cat rename(String newName) {
return copyWith(name: newName);
}
}
extension DogExtension on Dog {
Dog rename(String newName) {
return copyWith(name: newName);
}
}
extension AnimalExtension on Animal {
Animal rename(String newName) {
return copyWith(name: newName);
}
}
if your object is of type Animal it will use AnimalExtension, otherwise it will use the specific extension (or just override a method as you did with copyWith)
void main() {
Animal cat = Cat("Whiskers");
Animal dog = Dog("Buddy");
Animal renamedCat = cat.rename("Mittens");
Animal renamedDog = dog.rename("Max");
print("Old cat name: ${cat.name}, New cat name: ${renamedCat.name}");
print("Old dog name: ${dog.name}, New dog name: ${renamedDog.name}");
}
The other way to fix it is by setting the bound in the beggining of the Animal class
T copyWith<T extends Animal>({String? name});
The previous method didn't bound it correctly because the class is not aware of T, only the method
abstract class Animal<T extends Animal<T>> {
final String name;
Animal(this.name);
T copyWith({String? name});
}
class Cat extends Animal<Cat> {
Cat(super.name);
@override
Cat copyWith({String? name}) {
return Cat(name ?? this.name);
}
}
class Dog extends Animal<Dog> {
Dog(super.name);
@override
Dog copyWith({String? name}) {
return Dog(name ?? this.name);
}
}
extension AnimalExtension<T extends Animal<T>> on T {
T rename(String newName) {
return copyWith(name: newName);
}
}
Now the system knows that copywith is of type Animal<T>
:
- Cat is of type
Animal<Cat>
- Dog is of type
Animal<Dog>
- AnimalExtension returns a type of
Animal<T>
, which uses copyWith that is the sameAnimal<T>
(T =Animal<Dog>
, copyWith returnsAnimal<Dog>
and so on)