Naar inhoud springen

Typesysteem

Uit Wikipedia, de vrije encyclopedie
(Doorverwezen vanaf Type-systeem)

In de informatica definieert een typesysteem hoe een programmeertaal gegevens groepeert in verschillende gegevenstypen, en hoe deze typen gebruikt en gecombineerd kunnen worden.

Een type specificeert een verzameling waarden die bepaalde overeenkomsten hebben. Met elk type is een verzameling bewerkingen geassocieerd die op waarden van dat type toegepast kunnen (mogen) worden. Een van de belangrijkste verschillen tussen een high level-programmeertaal en een low level-programmeertaal is dat een low level-taal geen typesysteem heeft: zo'n taal kent alleen bytes en words.[1]

Het typesysteem van een programmeertaal heeft de volgende functies:

  • Veiligheid - Aan de hand van types kan een compiler (of interpreter) bepaalde fouten (zogenaamde type errors) herkennen en de programmeur hiervan op de hoogte stellen. De mate waarin de compiler op basis van typen fouten kan opsporen hangt af van het typesysteem: sterke typering biedt meer veiligheid dan zwakke typering.
  • Optimalisatie - Als de types van expressies al tijdens het compileren bekend zijn (static typing) kan de compiler vaak bepaalde optimalisaties toepassen.
  • Documentatie - Wanneer de types van gebruikte variabelen en functieparameters zijn gespecificeerd in een programma bieden ze de lezer van de broncode aanwijzingen over de aard en het gebruik van deze variabelen en parameters.

Type checking

[bewerken | brontekst bewerken]

Wanneer een compiler of interpreter een programma compileert of uitvoert, controleert hij of iedere bewerking type correct is. Dit wordt type checking genoemd. Als de compiler of interpreter een bewerking tegenkomt die niet type correct is, zoals het vermenigvuldigen van een string met een boolean, wordt de uitvoering afgebroken en een foutbericht afgedrukt.

Statische typering

[bewerken | brontekst bewerken]

Als een programmeertaal statisch getypeerd is, vindt type checking plaats tijdens het compileren (compile-time), en niet tijdens het uitvoeren (run-time) van het programma. Als de compiler een fout vindt wordt de compilatie afgebroken en de programmeur wordt op de hoogte gesteld van het probleem. Voorbeelden van talen die statisch getypeerd zijn, zijn C, Java, Scala en Haskell.

Statische typering heeft een paar voordelen:

  • Omdat alle typechecks al tijdens het compileren uitgevoerd zijn, is een gecompileerd programma altijd type-correct. Tijdens het uitvoeren van het programma hoeven dus geen type-checks meer plaats te vinden. Hoewel het compileren iets langer duurt, zal het resulterende programma sneller (en kleiner) zijn.
  • Bij statische typering worden type-errors al tijdens het ontwikkelen van het programma door de compiler ontdekt. Statische typering zorgt dus voor een soort basale automatische programmaverificatie.
  • De compiler heeft meer mogelijkheden voor optimalisatie.

Inferred typing

[bewerken | brontekst bewerken]
Zie Type-inferentie voor het hoofdartikel over dit onderwerp.

Niet alle statisch getypeerde talen verplichten de programmeur om van alle variabelen en parameters expliciet te vermelden welk type ze hebben. Bij talen waarbij dit niet het geval is (zoals Haskell en OCaml), bepaalt de compiler zelf de typen van variabelen en parameters (in het algemeen: van alle expressies). Dit wordt type inference genoemd.

Dynamische typering

[bewerken | brontekst bewerken]

Een programmeertaal wordt dynamisch getypeerd genoemd als type checking plaatstvindt tijdens de uitvoering van het programma, in plaats van tijdens het compileren van het programma. In een dynamisch getypeerde taal hebben waarden wél een vast type, maar variabelen en expressies niet.

In tegenstelling tot statische typering geeft dynamische typering de programmeur meer vrijheid. Dit leidt ertoe dat programma's sneller geschreven kunnen worden en (vaak) korter zijn. Voorbeelden van dynamisch getypeerde talen zijn: Perl, PHP, Python en Lisp. Fouten zouden zich echter kunnen manifesteren tijdens de daadwerkelijke uitvoering van het programma.

Sommige statisch getypeerde talen bieden de mogelijkheid om gebruik te maken van dynamische typering door middel van een mechanisme dat casting wordt genoemd. Bij casting wordt een expressie tijdens run-time geconverteerd naar een ander type. In dit geval vindt geen type check plaats tijdens compilatie. Casting kan echter leiden tot onverwachte fouten tijdens de uitvoering, en wordt daarom unsafe (onveilig) genoemd. De meeste statisch getypeerde talen bieden een vorm van casting.

Er zijn ook programmeertalen die de mogelijkheid bieden om statische typering voor delen van het programma 'uit te schakelen'. Voorbeelden zijn Ada en haXe.[2]

Sterke typering

[bewerken | brontekst bewerken]

Over de criteria waaraan een programmeertaal moet voldoen om als sterk getypeerd te gelden bestaat weinig consensus. Er zijn talen die soms als sterk getypeerd en soms als zwak getypeerd aangeduid worden, afhankelijk van de criteria die de auteur hanteert.

In het algemeen geldt dat een programmeertaal die sterk getypeerd is een aantal strikte regels heeft wat betreft de manier waarop verschillende typen gegevens gebruikt mogen worden en welke types gecombineerd mogen worden. Hoewel vrijwel iedere programmeertaal de mogelijkheid biedt om van het ene naar het andere type te converteren, zijn deze mogelijkheden in een sterk getypeerde taal meestal (zeer) beperkt. Een voorbeeld van een conversie die ook in de meeste sterk getypeerde talen is toegestaan, is die van gehele getallen naar drijvendekommagetallen. Dit betekent dat het is toegestaan om gehele getallen (integers) en drijvendekommagetallen door elkaar te gebruiken in rekenkundige expressies.

Voorbeelden van sterk getypeerde talen zijn Java, Pascal, Scala en Haskell.

Maar is een taal als C nu sterk getypeerd, of niet? De meningen hierover verschillen. Soms wordt het volgende criterium gebruikt: een taal is sterk getypeerd als iedere variabele en parameter een bijbehorend type heeft, en dit type is bekend tijdens compile-time.[3] In dat geval is C sterk getypeerd, hoewel je wel kunt zeggen dat Java en Pascal sterker getypeerd zijn dan C, omdat C meer impliciete conversies toestaat dan de andere twee.

Een andere (meer strikte) definitie is: een taal is sterk getypeerd als typefouten altijd ontdekt worden. Hiervoor is het noodzakelijk dat de types van alle gebruikte variabelen en parameters gedetermineerd kunnen worden, tijdens compilatie of tijdens de uitvoering van het programma. In dat geval is C niet sterk getypeerd: de types van unions worden in C niet gecheckt.[3]

Soms wordt sterke typering ook gelijkgesteld aan statische typering. In dat geval is C sterk getypeerd.

Zwakke typering

[bewerken | brontekst bewerken]

Een taal die niet sterk getypeerd is, is zwak getypeerd. Bij zwakke typering geldt hetzelfde probleem als bij sterke typering: de term wordt niet eenduidig toegepast, en sommige talen die door de ene auteur als zwak getypeerd worden aangeduid, worden door de andere auteur als sterk getypeerd aangeduid.

In tegenstelling tot talen met sterke typering, leggen talen met een zwakke typering weinig regels op aan de programmeur als het gaat om de manier waarop typen gebruikt en gecombineerd worden. Meestal betekent dit dat de compiler (of interpreter) zorgt voor een correcte conversie van het ene type naar het andere wanneer dat noodzakelijk is (implicit conversion).

Een voordeel van zwakke typering is dat de compiler of interpreter zorg draagt voor het converteren van typen, en de programmeur dit dus niet hoeft te doen. Een nadeel van zwakke typering is dat de compiler of interpreter minder makkelijk mogelijke fouten in een programma kan ontdekken.

Zwak vs. sterk en statisch vs. dynamisch

[bewerken | brontekst bewerken]

Hoewel het afhankelijk is van de gehanteerde definitie van sterke typering, geldt (wanneer sterke typering niet gelijkgesteld wordt aan statische typering) dat de twee categorieën onafhankelijk van elkaar zijn. Of een taal statisch of dynamisch getypeerd is hangt af van wanneer type checking plaatsvindt. Of een taal sterk of zwak getypeerd is hangt af van de mate waarin type checking plaatsvindt: hoe meer type checking er wordt gedaan (hoe strenger een taal is), hoe sterker deze getypeerd is.

Safe en unsafe typing (veilige en onveilige typering)

[bewerken | brontekst bewerken]

Een taal is type-safe als bewerkingen en type-conversies die tot een onveilige toestand leiden niet toegestaan zijn. Als voorbeeld nemen we het volgende C fragment:

int x = 4;
char y[] = "37";
char *z = x+y;

De variabele z wijst nu naar een adres waarvan de inhoud ongedefinieerd is. Stel dat de compiler voor y[] een geheugenruimte van 3 bytes gereserveerd heeft, startend op adres 100, dan ziet het geheugen er als volgt uit:

Variabele wijst naar adres inhoud
y 100 '3'
101 '7'
102 '\0'
103 ??
z 104 ??

Wat zich op het adres bevindt waar z naar wijst, is niet te voorspellen. Misschien willekeurige data, of het resultaat van een vorige berekening. Als het programma verderop de inhoud van het adres waar z naar wijst probeert te inspecteren zal het waarschijnlijk vastlopen, of een willekeurig resultaat opleveren. Het bovenstaande C programma is correct getypeerd, maar is niet veilig. Een taal waarin dat mogelijk is noemen we unsafely typed.

Een taal die berekeningen met pointers mogelijk maakt is nooit type safe. Veel talen gebruiken daarom geen pointers, maar references (bijvoorbeeld Java en Perl). De enige manier om een reference te creëren is door het adres te nemen van data die al bestaat (en dus geïnitialiseerd is): een reference kan dan alleen naar bestaande, geldige data wijzen.

Zie Duck-typing voor het hoofdartikel over dit onderwerp.

Wanneer je in een objectgeoriënteerde programmeertaal een methode van een object aanroept, controleert de compiler (of interpreter) het type van dat object. Het type van een object is de klasse waarvan het object een instantie is. Vervolgens controleert de compiler of de klasse waartoe het object behoort inderdaad de aangeroepen methode heeft. Is dat niet het geval, dan wordt er een fout gegenereerd.

Voorbeeld: stel we hebben een object gecreëerd met de volgende declaratie: mijnObject = new Object(). Als de compiler nu het volgende statement tegenkomt: mijnObject.start(), weet de compiler dat mijnObject van het type Object is. Vervolgens controleert de compiler of de klasse Object inderdaad een methode start() heeft. Is dit niet het geval dan wordt een error gegenereerd. De manier waarop je een object mag gebruiken hangt dus af van het type van dat object.

Dat is echter niet noodzakelijk. Als de bewerkingen die op een object toegestaan zijn alleen afhangen van het object zelf, spreken we over duck-typing. Bij duck-typing kijkt de compiler naar het object zelf, en niet naar de klasse (het type) waartoe het behoort.

Een compiler van een taal met duck-typing kijkt in het bovenstaande voorbeeld dus niet of de klasse Object een methode start() heeft, maar alleen of het object mijnObject een methode start() heeft. In een taal die het toestaat om nieuwe eigenschappen en methoden voor objecten te definiëren (methoden en eigenschappen die de klasse waarvan het object een instantie is niet heeft), maakt dit een wezenlijk verschil.

Voorbeelden van programmeertalen met duck-typing zijn JavaScript, Python en Ruby.