Kryds type - Intersection type
I typeteori kan en krydstype tildeles værdier, der kan tildeles både typen og typen . Denne værdi kan gives krydstypen i et krydstypesystem . Generelt, hvis værdiområderne for to typer overlapper hinanden, kan en værdi, der tilhører skæringspunktet mellem de to områder, tildeles skæringstypen for disse to typer. En sådan værdi kan sikkert overføres som argument til funktioner, der forventer en af de to typer. For eksempel implementerer klassen både og og grænsefladerne i Java . Derfor kan et objekt af type sikkert overføres til funktioner, der forventer et argument af typen og til funktioner, der forventer et argument af typen .
BooleanSerializableComparableBooleanSerializableComparable
Krydsstyper er sammensatte datatyper . I lighed med produkttyper bruges de til at tildele flere typer til et objekt. Produkttyper tildeles dog tupler , så hvert tupelelement tildeles en bestemt produkttypekomponent. Til sammenligning er underliggende objekter af skæringspunkter ikke nødvendigvis sammensatte. En begrænset form for krydstyper er forfiningstyper .
Krydstyper er nyttige til at beskrive overbelastede funktioner . Hvis f.eks. Funktionstypen tager et tal som et argument og returnerer et tal, og det er funktionstypen, der tager en streng som et argument og returnerer en streng, kan skæringspunktet mellem disse to typer bruges til at beskrive ( overbelastede) funktioner, der gør det ene eller det andet, baseret på hvilken type input de får.
number => numberstring => string
Moderne programmeringssprog, herunder Ceylon , Flow, Java , Scala , TypeScript og Whiley (se sammenligning af sprog med krydsetyper ), bruger skæringstyper til at kombinere grænsefladespecifikationer og til at udtrykke ad hoc polymorfisme . Som supplement til parametrisk polymorfisme kan krydsetyper bruges til at undgå klassehierarkiforurening fra tværgående bekymringer og reducere kedelpladekode , som vist i TypeScript-eksemplet nedenfor.
Den form teoretisk studie af kryds typer omtales som den disciplin krydset typen . Bemærkelsesværdigt kan programafslutning præcist karakteriseres ved hjælp af krydsstyper.
TypeScript -eksempel
TypeScript understøtter skæringstyper, forbedrer typesystemets udtryksfuldhed og reducerer potentiel klassehierarkistørrelse, demonstreret som følger.
Følgende program kode definerer klasserne Chicken, Cowog RandomNumberGeneratorsom hver har en fremgangsmåde producereturnere et formål med begge typer Egg, Milkeller number. Derudover de funktioner eatEggog drinkMilkkræver argumenter af type Eggog Milkhhv.
class Egg { private kind: "Egg" }
class Milk { private kind: "Milk" }
//produces eggs
class Chicken { produce() { return new Egg(); } }
//produces milk
class Cow { produce() { return new Milk(); } }
//produces a random number
class RandomNumberGenerator { produce() { return Math.random(); } }
//requires an egg
function eatEgg(egg: Egg) {
return "I ate an egg.";
}
//requires milk
function drinkMilk(milk: Milk) {
return "I drank some milk.";
}
Den følgende programkode definerer den ad hoc polymorfe funktion, animalToFoodder påkalder medlemsfunktionen producefor det givne objekt animal. Funktionen animalToFoodhar to typeanmærkninger, nemlig og , forbundet via krydsetypekonstruktoren . Specifikt, når det anvendes på et argument af typen, returnerer et objekt af typen type , og når det anvendes på et argument af typen, returneres et objekt af typen . Ideelt set burde det ikke være anvendeligt på ethvert objekt, der (muligvis ved en tilfældighed) har en metode.
((_: Chicken) => Egg)((_: Cow) => Milk)&animalToFoodChickenEggCowMilkanimalToFoodproduce
//given a chicken, produces an egg; given a cow, produces milk
let animalToFood: ((_: Chicken) => Egg) & ((_: Cow) => Milk) =
function (animal: any) {
return animal.produce();
};
Endelig demonstrerer følgende programkode typen sikker brug af ovenstående definitioner.
var chicken = new Chicken();
var cow = new Cow();
var randomNumberGenerator = new RandomNumberGenerator();
console.log(chicken.produce()); //Egg { }
console.log(cow.produce()); //Milk { }
console.log(randomNumberGenerator.produce()); //0.2626353555444987
console.log(animalToFood(chicken)); //Egg { }
console.log(animalToFood(cow)); //Milk { }
//console.log(animalToFood(randomNumberGenerator)); //ERROR: Argument of type 'RandomNumberGenerator' is not assignable to parameter of type 'Cow'
console.log(eatEgg(animalToFood(chicken))); //I ate an egg.
//console.log(eatEgg(animalToFood(cow))); //ERROR: Argument of type 'Milk' is not assignable to parameter of type 'Egg'
console.log(drinkMilk(animalToFood(cow))); //I drank some milk.
//console.log(drinkMilk(animalToFood(chicken))); //ERROR: Argument of type 'Egg' is not assignable to parameter of type 'Milk'
Ovenstående programkode har følgende egenskaber:
- Linjer 1-3 skabe objekter
chicken,cowograndomNumberGeneratori deres respektive type. - Linje 5-7 udskrives for de tidligere oprettede objekter de respektive resultater (leveres som kommentarer) ved påkaldelse
produce. - Linje 9 (hhv. 10) viser typen sikker brug af metoden
animalToFoodanvendt påchicken(hhv.cow). - Linje 11, hvis den ikke blev kommenteret, ville resultere i en typefejl på kompileringstidspunktet. Selv om gennemførelsen af
animalToFoodkunne påberåbe sigproducemetoderandomNumberGenerator, den typen annotation afanimalToFoodtillader det ikke. Dette er i overensstemmelse med den tilsigtede betydning afanimalToFood. - Linje 13 (hhv. 15) viser, at anvendelse
animalToFoodpåchicken(hhv.cow) Resulterer i et objekt af typenEgg(hhv.Milk). - Linje 14 (hhv. 16) viser, at ansøgning
animalToFoodomcow(hhv.chicken) Ikke resulterer i et objekt af typenEgg(hhv.Milk). Derfor ville linje 14 (hhv. 16) resultere i en typefejl på kompileringstidspunktet, hvis den ikke kommenterede.
Sammenligning med arv
Ovenstående minimalistiske eksempel kan realiseres ved hjælp af arv , for eksempel ved at udlede klasserne Chickenog Cowfra en basisklasse Animal. I en større indstilling kan dette imidlertid være ufordelagtigt. Indførelse af nye klasser i et klassehierarki er ikke nødvendigvis berettiget til tværgående bekymringer eller måske direkte umuligt, for eksempel ved brug af et eksternt bibliotek. Det er forestillingsværdigt, at ovenstående eksempel kan udvides med følgende klasser:
- en klasse
Horse, der ikke har enproducemetode; - en klasse,
Sheepder har enproducemetode, der vender tilbageWool; - en klasse,
Pigder har enproducemetode, som kun kan bruges én gang, vender tilbageMeat.
Dette kan kræve yderligere klasser (eller grænseflader), der angiver, om en produktionsmetode er tilgængelig, om produktmetoden returnerer mad, og om produktmetoden kan bruges gentagne gange. Samlet set kan dette forurene klassehierarkiet.
Sammenligning med andeskrivning
Ovenstående minimalistiske eksempel viser allerede, at andetypning er mindre egnet til at realisere det givne scenario. Selvom klassen RandomNumberGeneratorindeholder en producemetode, skal objektet randomNumberGeneratorikke være et gyldigt argument for animalToFood. Ovenstående eksempel kan realiseres ved hjælp af andetypning, f.eks. Ved at introducere et nyt felt argumentForAnimalToFoodtil klasserne Chickenog Cowangive, at objekter af tilsvarende type er gyldige argumenter for animalToFood. Dette ville imidlertid ikke kun øge størrelsen af de respektive klasser (især med introduktionen af flere metoder, der ligner animalToFood), men er også en ikke-lokal tilgang mht animalToFood.
Sammenligning med funktionel overbelastning
Ovenstående eksempel kan realiseres ved hjælp af funktionsoverbelastning , for eksempel ved at implementere to metoder og . I TypeScript er en sådan løsning næsten identisk med det angivne eksempel. Andre programmeringssprog, f.eks. Java , kræver forskellige implementeringer af den overbelastede metode. Dette kan føre til enten kodeduplikation eller kogepladekode .
animalToFood(animal: Chicken): EgganimalToFood(animal: Cow): Milk
Sammenligning med besøgsmønsteret
Ovenstående eksempel kan realiseres ved hjælp af besøgsmønsteret . Det ville kræve, at hver dyreklasse implementerede en acceptmetode, der accepterer et objekt, der implementerer grænsefladen AnimalVisitor(tilføjelse af ikke-lokal kedelpladekode ). Funktionen animalToFoodville blive realiseret som visitmetoden til en implementering af AnimalVisitor. Desværre ville forbindelsen mellem inputtypen ( Chickeneller Cow) og resultattypen ( Eggeller Milk) være vanskelig at repræsentere.
Begrænsninger
På den ene side, skæringspunkterne typer kan anvendes til lokalt at Påtegn forskellige typer til en funktion uden at indføre nye klasser (eller grænseflader) til klassen hierarkiet. På den anden side kræver denne fremgangsmåde, at alle mulige argumenttyper og resultattyper skal specificeres eksplicit. Hvis en funktions adfærd kan specificeres præcist ved enten en samlet grænseflade, parametrisk polymorfisme eller andetypning , er krydsetypernes verbale karakter ugunstig. Derfor bør krydsningstyper betragtes som komplementære til eksisterende specifikationsmetoder.
Afhængig skæringstype
En afhængig skæringstype , betegnet , er en afhængig type , hvor typen kan afhænge af termvariablen . Navnlig hvis et udtryk har den afhængige vejkryds typen , så udtrykket har både type og typen , hvor er den type, der resulterer fra at erstatte alle forekomster af udtrykket variabel i med udtrykket .
Scala eksempel
Scala understøtter typedeklarationer som objektmedlemmer. Dette gør det muligt for en type af et objektmedlem at afhænge af værdien af et andet medlem, som kaldes en stiafhængig type . For eksempel definerer følgende programtekst en Scala -egenskab Witness, som kan bruges til at implementere singleton -mønsteret .
trait Witness {
type T
val value: T {}
}
Ovenstående træk Witnesserklærer medlemmet T, som kan tildeles en type som dets værdi, og medlemmet value, som kan tildeles en værdi af typen T. Den følgende programtekst definerer et objekt booleanWitnesssom forekomst af ovenstående træk Witness. Objektet booleanWitnessdefinerer typen Tsom Booleanog værdien valuesom true. For eksempel udførelse af udskrifter på konsollen.
System.out.println(booleanWitness.value)true
object booleanWitness extends Witness {
type T = Boolean
val value = true
}
Lad være typen (specifikt en rekordtype ) af objekter, der har et medlem af typen . I ovenstående eksempel kan objektet tildeles den afhængige skæringstype . Begrundelsen er som følger. Objektet har det medlem, der er tildelt typen som dens værdi. Siden er en type, har objektet typen . Derudover har objektet det medlem, der er tildelt værdien af typen . Da værdien af er , har objektet typen . Samlet set har objektet skæringstypen . Derfor præsenterer objektet selvreference som afhængighed og har den afhængige skæringstype .
booleanWitnessbooleanWitnessTBooleanBooleanbooleanWitnessbooleanWitnessvaluetrueBooleanbooleanWitness.TBooleanbooleanWitnessbooleanWitnessbooleanWitness
Alternativt kan ovenstående minimalistiske eksempel beskrives ved hjælp af afhængige posttyper . I sammenligning med afhængige skæringspunkter udgør afhængige registreringstyper et strengt mere specialiseret type teoretisk begreb.
Skæringspunkt for en type familie
Et skæringspunkt mellem en typefamilie , betegnet , er en afhængig type , hvor typen kan afhænge af termvariablen . Navnlig hvis et udtryk har den type , så for hver sigt af typen , udtrykket har typen . Denne forestilling kaldes også implicit Pi -type , idet man bemærker, at argumentet ikke holdes på terminsniveau.
Sammenligning af sprog med krydsetyper
| Sprog | Aktivt udviklet | Paradigme (r) | Status | Funktioner |
|---|---|---|---|---|
| C# | Ja | Under diskussion | ? | |
| Ceylon | Ja | Understøttet |
|
|
| F# | Ja | Under diskussion | ? | |
| Flyde | Ja | Understøttet |
|
|
| Forsythe | Ingen | Understøttet |
|
|
| Java | Ja | Understøttet |
|
|
| PHP | Ja | Understøttet |
|
|
| Scala | Ja | Understøttet |
|
|
| TypeScript | Ja | Understøttet |
|
|
| Mens | Ja | Understøttet | ? |