Programmiertheorie: Variante

Hallo, mein Name ist Dmitry Karlovsky und ich ... ich möchte Ihnen etwas über die grundlegenden Merkmale von Typsystemen erzählen, die durch das Prisma der Implementierung einer bestimmten Sprache, die aufgrund der evolutionären Entwicklung viele Atavismen aufweist, häufig oder nicht oder nicht richtig verstanden werden. Versuchen Sie daher, die Probleme mit einem neuen Blick zu betrachten, auch wenn Sie glauben, zu wissen, was „Variation“ ist. Wir werden von den Grundlagen ausgehen, sodass auch Anfänger alles verstehen. Und wir machen ohne Wasser weiter, so dass auch die Profis nützlich sind, um ihr Wissen zu strukturieren. Codebeispiele werden in einer Pseudosprache ähnlich wie TypeScript vorliegen. Anschließend werden die Ansätze mehrerer realer Sprachen untersucht. Und wenn Sie Ihre eigene Sprache entwickeln, hilft Ihnen dieser Artikel dabei, nicht auf den Rechen eines anderen zu treten.


Was ist, wenn es Füchse gibt?


Argumente und Parameter


Der Parameter ist das, was wir akzeptieren. Bei der Beschreibung des Parametertyps legen wir eine Einschränkung für die Typen fest, die an uns übergeben werden können. Einige Beispiele:


//   function log( id : string | number ) {} //   class Logger { constructor( readonly id : Natural ) {} } //   class Node< Id extends Number > { id : Id } 

Ein Argument ist das, was wir weitergeben. Zum Zeitpunkt der Übertragung hat das Argument immer einen bestimmten Typ. Bei der statischen Analyse ist ein bestimmter Typ möglicherweise nicht bekannt, weshalb der Compiler wieder mit Typeinschränkungen arbeitet. Einige Beispiele:


 log( 123 ) //   new Logger( promptStringOrNumber( 'Enter id' ) ) //       new Node( 'root' ) //   ,   

Untertypen


Typen können eine Hierarchie bilden. Ein Subtyp ist ein Sonderfall eines Supertyps . Ein Subtyp kann gebildet werden, indem die Menge der möglichen Werte eines Supertyps eingegrenzt wird. Beispielsweise ist der Typ Natural ein Subtyp von Integer und Positive. Und alle drei sind gleichzeitig Untertypen von Real. Und der Prime-Typ ist ein Subtyp von allen oben genannten. Gleichzeitig überlappen sich die Typen Positive und Integer, aber keiner von ihnen schränkt den anderen ein.


Bild


Eine andere Möglichkeit, einen Untertyp zu bilden, besteht darin, ihn zu erweitern , indem Sie ihn mit einem anderen orthogonal dazu stehenden Typ kombinieren. Beispielsweise gibt es eine „Farbfigur“ mit der Eigenschaft „Farbe“ und ein „Quadrat“ mit der Eigenschaft „Höhe“. Durch die Kombination dieser Typen erhalten wir ein "Farbquadrat". Wenn wir einen "Kreis" mit seinem "Radius" hinzufügen, erhalten wir einen "Farbzylinder".


Bild


Hierarchien


Für die weitere Darstellung benötigen wir eine kleine Hierarchie von Tieren und eine ähnliche Hierarchie von Zellen.


 abstract class Animal {} abstract class Pet extends Animal {} class Cat extends Pet {} class Dog extends Pet {} class Fox extends Animal {} class AnimalCage { content : Animal } class PetCage extends AnimalCage { content : Pet } class CatCage extends PetCage { content : Cat } class DogCage extends PetCage { content : Dog } class FoxCage extends AnimalCage { content : Fox } 

Alles unten ist eine Verengung wie oben. Ein Käfig mit einem Haustier kann nur Haustiere enthalten, jedoch keine Wildtiere. Ein Käfig mit einem Hund kann nur Hunde enthalten.


Bild


Kovarianz


Das einfachste und verständlichste ist die Einschränkung eines Supertyps oder einer Kovarianz. Im folgenden Beispiel ist der Funktionsparameter kovariant zu dem für ihn angegebenen Typ. Das heißt, die Funktion kann sowohl diesen Typ selbst als auch einen beliebigen Subtyp davon akzeptieren, jedoch keine Supertypen oder andere Typen.


 function touchPet( cage : PetCage ) : void { log( `touch ${cage.content}` ) } touchPet( new AnimalCage ) // forbid touchPet( new PetCage ) // allow touchPet( new CatCage ) // allow touchPet( new DogCage ) // allow touchPet( new FoxCage ) // forbid 

Bild


Da wir im Käfig nichts verändern, können wir mit der Katze sicher Funktionen auf den Käfig übertragen, da es sich nur um einen Sonderfall des Käfigs mit einem Haustier handelt.


Zuwiderhandlung


Etwas schwieriger ist es, die Einschränkung oder Kontravarianz von Subtypen zu verstehen. Im folgenden Beispiel steht der Funktionsparameter im Widerspruch zu dem dafür angegebenen Typ. Das heißt, die Funktion kann sowohl diesen Typ selbst als auch einen ihrer Supertypen akzeptieren, jedoch keine Subtypen oder anderen Typen.


 function pushPet( cage : PetCage ) : void { const Pet = random() > .5 ? Cat : Dog cage.content = new Pet } pushPet( new AnimalCage ) // allow pushPet( new PetCage ) // allow pushPet( new CatCage ) // forbid pushPet( new DogCage ) // forbid pushPet( new FoxCage ) // forbid 

Bild


Wir können den Käfig nicht mit der Katze passieren, da die Funktion den Hund dorthin bringen kann, was nicht zulässig ist. Der Käfig kann aber mit jedem Tier sicher überführt werden, da sowohl die Katze als auch der Hund dort untergebracht werden können.


Invarianz


Subtyp und Supertyp können gleichzeitig begrenzt werden. Ein solcher Fall heißt Invarianz. Im folgenden Beispiel ist der Funktionsparameter für den dafür angegebenen Typ unveränderlich. Das heißt, die Funktion kann nur den angegebenen Typ akzeptieren und nicht mehr.


 function replacePet( cage : PetCage ) : void { touchPet( cage ) pushPet( cage ) } replacePet( new AnimalCage ) // forbid replacePet( new PetCage ) // allow replacePet( new CatCage ) // forbid replacePet( new DogCage ) // forbid replacePet( new FoxCage ) // forbid 

Bild


Die Funktion replacePet erbt die Einschränkungen der intern verwendeten Funktionen: Sie hat die Einschränkung des Typs von pushPet und die Einschränkung des Subtyps von pushPet . Wenn wir ihr mit einem Tier einen Käfig geben, kann sie ihn nicht auf die touchPet-Funktion übertragen, die nicht weiß, wie man mit Füchsen arbeitet (ein wildes Tier beißt einfach einen Finger ab). Und wenn wir den Käfig mit der Katze übertragen, funktioniert es nicht, pushPet .


Bivarianz


Man kann nur die exotische Abwesenheit von Beschränkungen erwähnen - Bivarianz. Im folgenden Beispiel kann eine Funktion jeden Typ akzeptieren, der ein Subtyp oder ein Subtyp ist.


 function enshurePet( cage : PetCage ) : void { if( cage.content instanceof Pet ) return pushPet( cage ) } replacePet( new AnimalCage ) // allow replacePet( new PetCage ) // allow replacePet( new CatCage ) // allow replacePet( new DogCage ) // allow replacePet( new FoxCage ) // forbid 

Bild


Darin können Sie den Käfig mit dem Tier übertragen. Dann prüft sie, ob sich ein Haustier im Käfig befindet, andernfalls steckt sie es in ein zufälliges Haustier. Und Sie können zum Beispiel einen Käfig mit einer Katze übertragen, dann wird sie einfach nichts tun.


Verallgemeinerungen


Einige glauben, dass Varianz irgendwie mit Verallgemeinerungen zusammenhängt. Oft, weil Abweichungen häufig am Beispiel von generischen Containern erklärt werden. In der ganzen Geschichte haben wir jedoch noch keine einzige Verallgemeinerung gehabt - es sind ganz konkrete Klassen:


 class AnimalCage { content : Animal } class PetCage extends AnimalCage { content : Pet } class CatCage extends PetCage { content : Cat } class DogCage extends PetCage { content : Dog } class FoxCage extends AnimalCage { content : Fox } 

Dies wurde gemacht, um zu zeigen, dass die Varianzprobleme nicht mit Verallgemeinerungen zusammenhängen. Verallgemeinerungen sind nur erforderlich, um das Kopieren und Einfügen zu reduzieren. Zum Beispiel kann der obige Code durch eine einfache Verallgemeinerung umgeschrieben werden:


 class Cage<Animal> { content : Animal } 

Und jetzt können Sie Instanzen beliebiger Zellen erstellen:


 const animalCage = new Cage<Animal>() const petCage = new Cage<Pet>() const catCage = new Cage<Cat>() const dogCage = new Cage<Dog>() const foxCage = new Cage<Fox>() 

Verjährungserklärung


Bitte beachten Sie, dass die Signaturen aller vier zuvor aufgelisteten Funktionen genau gleich sind:


 ( cage : PetCage )=> void 

Das heißt, eine solche Beschreibung der akzeptierten Parameter der Funktion ist nicht vollständig - daraus kann nicht abgeleitet werden, dass sie auf die Funktion übertragen werden kann. Nun, es sei denn, es ist deutlich zu sehen, dass es sich definitiv nicht lohnt, den Käfig mit dem Fuchs hinein zu führen.


Daher gibt es in modernen Sprachen ein Mittel, um explizit anzugeben, welche Typeinschränkungen ein Parameter hat. Zum Beispiel die in und out Modifikatoren in C #:


 interface ICageIn<in T> { T content { set; } } // contravariant generic parameter interface ICageOut<out T> { T content { get; } } // covariant generic parameter interface ICageInOut<T> { T content { get; set; } } // invariant generic parameter 

Leider ist es in C # für jede Variante von Modifikatoren erforderlich, auf einer separaten Schnittstelle zu starten. Soweit ich weiß, ist die Bivarianz in C # im Allgemeinen unaussprechlich.


Ausgabeparameter


Funktionen können nicht nur Werte akzeptieren, sondern auch zurückgeben. Im Allgemeinen darf der Rückgabewert nicht eins sein. Nehmen Sie zum Beispiel die Funktion, einen Käfig mit einem Haustier zu nehmen und zwei Haustiere zurückzugeben.


 function getPets( input : PetCage ) : [ Pet , Pet ] { return [ input.content , new Cat ] } 

Eine solche Funktion entspricht einer Funktion, die zusätzlich zu einem Eingangsparameter zwei weitere Ausgänge erhält.


 function getPets( input : PetCage , output1 : PetCage , output2 : PetCage ) : void { output1.content = input.content output2.content = new Cat } 

Externer Code reserviert zusätzlichen Speicher auf dem Stapel, sodass die Funktion alles ablegt, was sie zurückgeben möchte. Nach Fertigstellung kann der aufrufende Code diese Container bereits für eigene Zwecke verwenden.


Bild


Aus der Äquivalenz dieser beiden Funktionen folgt, dass die von der Funktion zurückgegebenen Werte im Gegensatz zu den Parametern immer dem angegebenen Ausgabetyp widersprechen. Denn eine Funktion kann in sie schreiben, aber nicht aus ihnen lesen.


Objektmethoden


Objektmethoden sind Funktionen, die einen zusätzlichen Zeiger auf ein Objekt als impliziten Parameter verwenden. Das heißt, die folgenden zwei Funktionen sind äquivalent.


 class PetCage { pushPet() : void { const Pet = random() > .5 ? Cat : Dog this.content = new Pet } } 

 function pushPet( this : PetCage ) : void { const Pet = random() > .5 ? Cat : Dog this.content = new Pet } 

Es ist jedoch wichtig zu beachten, dass eine Methode im Gegensatz zu einer regulären Funktion auch Mitglied der Klasse ist, die eine Erweiterung des Typs darstellt. Dies führt dazu, dass für Funktionen, die diese Methode aufrufen, eine zusätzliche Supertypeinschränkung angezeigt wird:


 function fillPetCage( cage : PetCage ) { cage.pushPet() } 

Bild


Wir können einen solchen pushPet nicht übergeben, wenn die pushPet Methode noch nicht definiert ist. Dies ist ähnlich wie bei der Invarianz, da sowohl von unten als auch von oben eine Einschränkung vorliegt. Die Position der pushPet Methode kann jedoch hierarchisch höher sein. Und hier wird die Überschreibbegrenzung sein.


Barbara-Lisk-Substitutionsprinzip (LSP)


Viele Leute denken, dass das Verhältnis eines Untertyps zu einem Untertyp nicht auf der Grundlage der zuvor erwähnten Methoden zum Eingrenzen und Erweitern des Typs bestimmt wird, sondern vielmehr durch die Möglichkeit, den Untertyp an jedem Ort zu ersetzen, an dem der Supertyp verwendet wird. Anscheinend liegt der Grund für diesen Fehler genau im LSP. Lesen wir jedoch die Definition dieses Prinzips sorgfältig durch und achten Sie darauf, was primär und was sekundär ist:


Funktionen, die den Basistyp verwenden, sollten in der Lage sein, Untertypen des Basistyps zu verwenden, ohne es zu wissen und ohne die Korrektheit des Programms zu verletzen.

Bei unveränderlichen Objekten (einschließlich Objekten, die nicht auf veränderbare Objekte verweisen) wird dieses Prinzip automatisch ausgeführt, da es keinen Platz gibt, von dem die Subtypeinschränkung abgeleitet werden kann.


Bei Mutables wird es immer schwieriger, da sich die folgenden beiden Situationen für das LSP-Prinzip gegenseitig ausschließen:


  1. Klasse A hat eine Unterklasse von B , wobei das Feld B::foo ein Subtyp von A::foo .
  2. Klasse A hat eine Methode, die das A::foo Feld ändern kann.

Dementsprechend gibt es nur noch drei Möglichkeiten:


  1. Verhindern Sie, dass Objekte ihre Feldtypen einschränken. Aber dann können Sie einen Elefanten für eine Katze in den Käfig schieben.
  2. Geleitet nicht von LSP, sondern von der Variabilität jedes Parameters jeder Funktion separat. Aber dann muss man viel nachdenken und dem Compiler erklären, wo die Typeinschränkungen liegen.
  3. Auf alles spucken und los geht's das Kloster funktionale Programmierung, bei der alle Objekte unveränderlich sind, was bedeutet, dass ihre akzeptierten Parameter kovariant zum deklarierten Typ sind.

TypeScript


Im Zeitskript ist die Logik einfach: Alle Parameter der Funktion werden als kovariant (was nicht wahr ist) und die Rückgabewerte als kontravariant (was wahr ist) betrachtet. Es wurde zuvor gezeigt, dass die Parameter einer Funktion absolut beliebig variieren können, je nachdem, was diese Funktion mit diesen Parametern macht. Daher sind dies die folgenden Vorfälle:


 abstract class Animal { is! : 'cat' | 'dog' | 'fox' } abstract class Pet extends Animal { is! : 'cat' | 'dog' } class Cat extends Pet { is! : 'cat' } class Dog extends Pet { is! : 'dog' } class Fox extends Animal { is! : 'fox' } class Cage<Animal> { content! : Animal } function pushPet( cage : Cage<Pet> ) : void { const Pet = Math.random() > .5 ? Cat : Dog cage.content = new Pet } pushPet( new Cage<Animal>() ) // forbid to push Pet to Animal Cage :-( pushPet( new Cage<Cat>() ) // allow to push Dog to Cat Cage :-( 

Um dieses Problem zu lösen, müssen Sie dem Compiler mit nicht trivialem Code helfen:


 function pushPet< PetCage extends Cage<Animal> >( cage: Cage<Pet> extends PetCage ? PetCage : never ): void { const Pet = Math.random() > .5 ? Cat : Dog cage.content = new Pet } pushPet( new Cage<Animal>() ) // allow :-) pushPet( new Cage<Pet>() ) // allow :-) pushPet( new Cage<Cat>() ) // forbid :-) pushPet( new Cage<Dog>() ) // forbid :-) pushPet( new Cage<Fox>() ) // forbid :-) 

Versuchen Sie es online


Flowjs


FlowJS verfügt über ein fortschrittlicheres Typensystem. Insbesondere kann in der Typbeschreibung die Variabilität für verallgemeinerte Parameter und für Objektfelder angegeben werden. In unserem Zellenbeispiel sieht es ungefähr so ​​aus:


 class Animal {} class Pet extends Animal {} class Cat extends Pet {} class Dog extends Pet {} class Fox extends Animal {} class Cage< Animal > { content : Animal } function touchPet( cage : { +content : Pet } ) : void { console.log( `touch ${typeof cage.content}` ) } function pushPet( cage: { -content: Pet } ): void { const Pet = Number((0: any)) > .5 ? Cat : Dog cage.content = new Pet } function replacePet( cage : { content : Pet } ) : void { touchPet( cage ) pushPet( cage ) } touchPet( new Cage<Animal> ) // forbid :-) touchPet( new Cage<Pet> ) // allow :-) touchPet( new Cage<Cat> ) // allow :-) touchPet( new Cage<Dog> ) // allow :-) touchPet( new Cage<Fox> ) // forbid :-) pushPet( new Cage<Animal> ) // allow :-) pushPet( new Cage<Pet> ) // allow :-) pushPet( new Cage<Cat> ) // forbid :-) pushPet( new Cage<Dog> ) // forbid :-) pushPet( new Cage<Fox> ) // forbid :-) replacePet( new Cage<Animal> ) // forbid :-) replacePet( new Cage<Pet> ) // allow :-) replacePet( new Cage<Cat> ) // forbid :-) replacePet( new Cage<Dog> ) // forbid :-) replacePet( new Cage<Fox>) // forbid :-) 

Versuchen Sie es online


Bivarianz ist hier unbeschreiblich. Leider konnte ich keine Möglichkeit finden, die Varianz bequemer einzustellen, ohne die Typen aller Felder explizit zu beschreiben. Zum Beispiel so etwas:


 function pushPet( cage: Contra< Cage<Pet> , 'content' > ): void { const Pet = Number((0: any)) > .5 ? Cat : Dog cage.content = new Pet } 

Cis


C # wurde ursprünglich ohne Verständnis von Variationen entwickelt. Später wurden jedoch Out- und Parameter-Modifikatoren hinzugefügt, die es dem Compiler ermöglichten, die übergebenen Argumenttypen korrekt zu überprüfen. Leider ist die Verwendung dieser Modifikatoren wiederum nicht sehr praktisch.


 using System; abstract class Animal {} abstract class Pet : Animal {} class Cat : Pet {} class Dog : Pet {} class Fox : Animal {} interface ICageIn<in T> { T content { set; } } interface ICageOut<out T> { T content { get; } } interface ICageInOut<T> { T content { get; set; } } class Cage<T> : ICageIn<T>, ICageOut<T>, ICageInOut<T> { public T content { get; set; } } public class Program { static void touchPet( ICageOut<Pet> cage ) { Console.WriteLine( cage.content ); } static void pushPet( ICageIn<Pet> cage ) { cage.content = new Dog(); } static void replacePet( ICageInOut<Pet> cage ) { touchPet( cage as ICageOut<Pet> ); pushPet( cage as ICageIn<Pet> ); } void enshurePet( Cage<Pet> cage ) { if( cage.content is Pet ) return; pushPet( cage as ICageIn<Pet> ); } public static void Main() { var animalCage = new Cage<Animal>(); var petCage = new Cage<Pet>(); var catCage = new Cage<Cat>(); var dogCage = new Cage<Dog>(); var foxCage = new Cage<Fox>(); touchPet( animalCage ); // forbid :-) touchPet( petCage ); // allow :-) touchPet( catCage ); // allow :-) touchPet( dogCage ); // allow :-) touchPet( foxCage ); // forbid :-) pushPet( animalCage ); // allow :-) pushPet( petCage ); // allow :-) pushPet( catCage ); // forbid :-) pushPet( dogCage ); // forbid :-) pushPet( foxCage ); // forbid :-) replacePet( animalCage ); // forbid :-) replacePet( petCage ); // allow :-) replacePet( catCage ); // forbid :-) replacePet( dogCage ); // forbid :-) replacePet( foxCage ); // forbid :-) } } 

Versuchen Sie es online


Java


In Java wurde die Möglichkeit, Variationen umzuschalten, erst spät und nur für verallgemeinerte Parameter hinzugefügt, die selbst erst vor relativ kurzer Zeit erschienen. Wenn der Parameter nicht verallgemeinert ist, treten Probleme auf.


 abstract class Animal {} abstract class Pet extends Animal {} class Cat extends Pet {} class Dog extends Pet {} class Fox extends Animal {} class Cage<T> { public T content; } public class Main { static void touchPet( Cage<? extends Pet> cage ) { System.out.println( cage.content ); } static void pushPet( Cage<? super Pet> cage ) { cage.content = new Dog(); } static void replacePet(Cage<Pet> cage ) { touchPet( cage ); pushPet( cage ); } void enshurePet( Cage<Pet> cage ) { if( cage.content instanceof Pet ) return; pushPet( cage ); } public static void main(String[] args) { Cage<Animal> animalCage = new Cage<Animal>(); Cage<Pet> petCage = new Cage<Pet>(); Cage<Cat> catCage = new Cage<Cat>(); Cage<Dog> dogCage = new Cage<Dog>(); Cage<Fox> foxCage = new Cage<Fox>(); touchPet( animalCage ); // forbid :-) touchPet( petCage ); // allow :-) touchPet( catCage ); // allow :-) touchPet( dogCage ); // allow :-) touchPet( foxCage ); // forbid :-) pushPet( animalCage ); // allow :-) pushPet( petCage ); // allow :-) pushPet( catCage ); // forbid :-) pushPet( dogCage ); // forbid :-) pushPet( foxCage ); // forbid :-) replacePet( animalCage ); // forbid :-) replacePet( petCage ); // allow :-) replacePet( catCage ); // forbid :-) replacePet( dogCage ); // forbid :-) replacePet( foxCage ); // forbid :-) } } 

Versuchen Sie es online


C ++


C ++ kann dank seines leistungsstarken Template-Systems verschiedene Variationen ausdrücken, aber es gibt natürlich viel Code.


 #include <iostream> #include <typeinfo> #include <type_traits> class Animal {}; class Pet: public Animal {}; class Cat: public Pet {}; class Dog: public Pet {}; class Fox: public Animal {}; template<class T> class Cage { public: T *content; }; template<class T, class = std::enable_if_t<std::is_base_of<Pet, T>::value>> void touchPet(const Cage<T> &cage) { std::cout << typeid(T).name(); } template<class T, class = std::enable_if_t<std::is_base_of<T, Pet>::value>> void pushPet(Cage<T> &cage) { cage.content = new Dog(); } void replacePet(Cage<Pet> &cage) { touchPet(cage); pushPet(cage); } int main(void) { Cage<Animal> animalCage {new Fox()}; Cage<Pet> petCage {new Cat()}; Cage<Cat> catCage {new Cat()}; Cage<Dog> dogCage {new Dog()}; Cage<Fox> foxCage {new Fox()}; touchPet( animalCage ); // forbid :-) touchPet( petCage ); // allow :-) touchPet( catCage ); // allow :-) touchPet( dogCage ); // allow :-) touchPet( foxCage ); // forbid :-) pushPet( animalCage ); // allow :-) pushPet( petCage ); // allow :-) pushPet( catCage ); // forbid :-) pushPet( dogCage ); // forbid :-) pushPet( foxCage ); // forbid :-) replacePet( animalCage ); // forbid :-) replacePet( petCage ); // allow :-) replacePet( catCage ); // forbid :-) replacePet( dogCage ); // forbid :-) replacePet( foxCage ); // forbid :-) return 0; } 

Versuchen Sie es online


D


D hat keine vernünftigen Mittel, um Varianz explizit anzuzeigen, aber er weiß, wie man Typen basierend auf ihrer Verwendung ableitet.


 import std.stdio, std.random; abstract class Animal {} abstract class Pet : Animal { string name; } class Cat : Pet {} class Dog : Pet {} class Fox : Animal {} class Cage(T) { T content; } void touchPet( PetCage )( PetCage cage ) { writeln( cage.content.name ); } void pushPet( PetCage )( PetCage cage ) { cage.content = ( uniform(0,2) > 0 ) ? new Dog() : new Cat(); } void replacePet( PetCage )( PetCage cage ) { touchPet( cage ); pushPet( cage); } void main() { Cage!Animal animalCage; Cage!Pet petCage; Cage!Cat catCage; Cage!Dog dogCage; Cage!Fox foxCage; animalCage.touchPet(); // forbid :-) petCage.touchPet(); // allow :-) catCage.touchPet(); // allow :-) dogCage.touchPet(); // allow :-) foxCage.touchPet(); // forbid :-) animalCage.pushPet(); // allow :-) petCage.pushPet(); // allow :-) catCage.pushPet(); // forbid :-) dogCage.pushPet(); // forbid :-) foxCage.pushPet(); // forbid :-) animalCage.replacePet(); // forbid :-) petCage.replacePet(); // allow :-) catCage.replacePet(); // forbid :-) dogCage.replacePet(); // forbid :-) foxCage.replacePet(); // forbid :-) } 

Versuchen Sie es online


Nachwort


Das ist alles für jetzt. Ich hoffe, das vorgestellte Material hat Ihnen geholfen, die Einschränkungen für Typen und deren Implementierung in verschiedenen Sprachen besser zu verstehen. Irgendwo besser, irgendwo schlechter, irgendwo auf keinen Fall, aber im Großen und Ganzen - so lala. Vielleicht sind Sie es, der die Sprache entwickelt, in der all dies bequem und typsicher implementiert wird. Nehmen Sie in der Zwischenzeit an unserem Telegramm-Chat teil, in dem wir manchmal die theoretischen Konzepte von Programmiersprachen diskutieren .

Source: https://habr.com/ru/post/de477448/


All Articles