Is there a way to redefine a type hint to a descendant class when extending an abstract class?
I wouldn't expect so, as it can break type hinting contracts. Suppose a function foo
took an AbstractFactory and was passed a SimpleFactory.
function foo(AbstractFactory $maker) {
$attr = new Attribute();
$maker->update($attr, 42);
}
...
$packager=new SimpleFactory();
foo($packager);
foo
calls update
and passes an Attribute to the factory, which it should take because the AbstractFactory::update
method signature promises it can take an Attribute. Bam! The SimpleFactory has an object of type it can't handle properly.
class Attribute {}
class SimpleAttribute extends Attribute {
public function spin() {...}
}
class SimpleFactory extends AbstractFactory {
public function update(SimpleAttribute $attr, $data) {
$attr->spin(); // This will fail when called from foo()
}
}
In contract terminology, descendent classes must honor the contracts of their ancestors, which means function parameters can get more basal/less specified/offer a weaker contract and return values can be more derived/more specified/offer a stronger contract. The principle is described for Eiffel (arguably the most popular design-by-contract language) in "An Eiffel Tutorial: Inheritance and Contracts". Weakening and strengthening of types are examples of contravariance and covariance, respectively.
In more theoretical terms, this is an example of LSP violation. No, not that LSP; the Liskov Substitution Principle, which states that objects of a subtype can be substituted for objects of a supertype. SimpleFactory
is a subtype of AbstractFactory
, and foo
takes an AbstractFactory
. Thus, according to LSP, foo
should take a SimpleFactory
. Doing so causes a "Call to undefined method" fatal error, which means LSP has been violated.
Typesafe / Type hinting in child classes of an abstract class in PHP
Having done a bit more digging around, and thanks to a helpful post from deceze, I think this idea hasn't been implemented. He has pointed out a similar answer it to which breaks the rules of SOLID.
http://en.wikipedia.org/wiki/SOLID
Basically - This says that I cannot change the parent class: this is the point of "extend".
I can only extend, not modify the parent class.
Changing the typesafeing is modifying the class.
HOWEVER - I would argue that this example doesn't break the rules of SOLID.
Typesafing with class that is extended from the base class should be allowed: You can never have a "User" that is not a "BaseModel"... so it is not modifying the parent class.
It looks like the only way to truly do this is
if ($item instanceof User)
{
// logic
}
In my case the parent class is probably enough to catch most issues, but it's a shame I can't typesafe it the way I intended.
[Thanks to deceze. Upvoted.]
Type hint for descendant classes
If I am not mistaken you get this error:
Declaration of PimpleConfigurer_Factories::configure() must be compatible with Pimple_Config::configure(Pimple $container) ...
Which means: If you define a method in a super class or in an interface, all sub classes (or classes implementing the interface) must use exactly this definition. You cannot use another type here.
As for your quote from the documentation:
If class or interface is specified as type hint then all its children or implementations are allowed too.
This only means that you can pass a variable which is of a certain type or all its children.
For example: Say you have the following classes:
class Car {
protected $hp = 50;
public function getHp() { return $this->hp; }
}
class SuperCar extends Car {
protected $hp = 700;
}
And a function (or method, no difference there) with type hint:
function showHorsePower(Car $car) {
echo $car->getHp();
}
You can now pass all objects of type Car and all its sub classes (here SuperCar) to this function, like:
showHorsePower(new Car());
showHorsePower(new SuperCar());
Tell methods of base class to use more restricted type hints in a derived class?
In more static languages you would create Store
as a generic class and use Car
as a type parameter when inheriting from Store
.
We can actually do that using the typing
module in python.
Here is a minimal example:
from typing import Generic, TypeVar
T = TypeVar('T') # this is the generic placeholder for a type
# Store is a generic class with type parameter T
class Store(Generic[T]):
def get(self) -> T: # this returns a T
return self.load_object()
def load_object(self) -> T: # this also returns a T
raise NotImplementedError
class Car:
def __init__(self, color):
self.color = color
# Now we inherit from the Store and use Car as the type parameter
class CarStore(Store[Car]):
def load_object(self):
return Car('red')
s = CarStore()
c = s.get()
print(c.color) # Code completion works and no warnings are shown
Edit:
To address ShadowRanger's note: If you want Car and all the products to have a common base class you can use the bound
parameter of the TypeVar
. Thank you juanpa.arrivillaga for the hint.
So we create a Product class and bind the TypeVar
to it.
class Product:
def get_id(self):
raise NotImplementedError
T = TypeVar('T', bound=Product)
Mypy will now complain about this:
class CarStore(Store[Car]):
def load_object(self):
return Car('red')
because a Car
is not a Product
. So let' change that, too:
class Car(Product):
def get_id(self):
return ...
def __init__(self, color):
self.color = color
And now, mypy is happy.
Edit2:
Here is the full code with some more annotations, that make even mypy --strict
happy.
from typing import Generic, TypeVar
class Product:
def get_id(self) -> int:
raise NotImplementedError
T = TypeVar('T', bound=Product)
class Store(Generic[T]):
def get(self) -> T:
return self.load_object()
def load_object(self) -> T:
raise NotImplementedError
class Car(Product):
def get_id(self) -> int:
return hash(self.color)
def __init__(self, color: str):
self.color = color
class CarStore(Store[Car]):
def load_object(self) -> Car:
return Car('red')
if __name__ == '__main__':
s = CarStore()
c = s.get()
print(c.color)
Php type hinting not getting along with interfaces and abstract classes?
php doesn't seem to be recognizing the signatures of
AnAbstractClass::method
andConcreteClass::method
as compatible.
PHP is correct, they're not compatible. By allowing only instances of AClass
(or its children) to be passed to ConcreteClass::method
, you're breaking the contract that AnAbstractClass
provides: Any of its subclasses must accept AnInterface
as an argument to its method()
.
If your example worked, and I had another class BClass
implementing AnInterface
, we'd have a situation where according to AnAbstractClass
, method()
should accept instances of BClass
, while according to ConcreteClass
, it shouldn't.
Change your signature for ConcreteClass::method
to match that of AnAbstractClass::method
.
Subclass in type hinting
When you specify cls: A
, you're saying that cls
expects an instance of type A
. The type hint to specify cls
as a class object for the type A
(or its subtypes) uses typing.Type
.
from typing import Type
def process_any_subclass_type_of_A(cls: Type[A]):
pass
From The type of class objects
:
Sometimes you want to talk about class objects that inherit from a
given class. This can be spelled asType[C]
whereC
is a class. In
other words, whenC
is the name of a class, usingC
to annotate an
argument declares that the argument is an instance ofC
(or of a
subclass ofC
), but usingType[C]
as an argument annotation declares
that the argument is a class object deriving fromC
(orC
itself).
Python: how to override type hint on an instance attribute in a subclass?
You can give a type hint on my_class
attribute in the beginning of class definition:
class SubclassOfFoo(Foo):
my_class: SubclassOfSomething # <- here
def get_something(self) -> SubclassOfSomething:
return SubclassOfSomething()
def do_something_special(self):
self.my_class.something_special()
After that there is no warning Unresolved attribute reference 'something_special' for class 'Something'
from PyCharm inspection because now my_class
is known to be SubclassOfSomething
not Something
.
Hinting abstract classes in TypeScript
We want to declare a value which, when called with new
, returns an Animal
. This is written as:
new() => Animal
Note the similarity to function types (() => Animal
).
The full code is then:
const all: Array<new() => Animal> = [Cat, Dog];
Or, using an interface for clarity:
interface AnimalConstructor {
new(): Animal;
}
const all: AnimalConstructor[] = [Cat, Dog];
Related Topics
Difference Between Require and Install VS Create-Project in Composer
How to Display Two Table Columns Per Row in PHP Loop
How to Add Commas to Numbers in PHP
Safe Alternatives to PHP Globals (Good Coding Practices)
If You Create a Variable Inside a If Statement Is It Available Outside the If Statement
Installing Imagemagick Extension with PHP/Windows
How to Send Money to Paypal Using PHP
PHP Difference Between Notice and Warning
Phpmailer - How to Remove Recipients
What Does [\S\S]* Mean in Regex in PHP
How Does {} Affect a MySQL Query in PHP
Fatal Error: Allowed Memory Size of 268435456 Bytes Exhausted (Tried to Allocate 71 Bytes)
Differencebetween Get_Result() and Store_Result() in PHP
Troubleshooting "The Use Statement with Non-Compound Name ... Has No Effect"