Jag har tänkt en del på typer på sistone. Jag har kommit fram till att jag gillar dem, och att de fortfarande är på uppgång i konventionella programmeringsspråk.
Jag pratar om ord som `int` i nedanstående programsats:
int sum = 0;
Detta bruk av ordet “typ” är bekant för användare av språk i C-familjen, inklusive Java och C#. I samband med att vi introducerar variabeln `sum` så deklarerar vi också att den ska ha en heltalstyp med en viss storlek. Om inte annat så att kompilatorn ska veta hur den ska allokera variabeln, och vilka operationer den ska tillåta.
“OK”, kan läsaren tycka. “Det låter ju rätt uppenbart.” Låt oss genast göra saker lite mindre självklara.
Min bakgrund är till stor del med dynamiska språk, som Perl, Python, och JavaScript. I vart och ett av de språken anger man _inte_ typen på en variabel man introducerar:
my $sum = 0; # Perl
sum = 0 # Python
let sum = 0; // JavaScript (ES6)
Med undantag för ytliga skillnader i syntax så gör de språken exakt samma sak i det här fallet: de introducerar variabeln _utan_ att ange att dess typ ska vara `int` (eller något annat, för den delen). Det är lite det vi menar med “dynamiskt språk”.
En av de stora säljpunkterna med de dynamiska språken är att man kan skriva kod utan att vara så nitisk med sina typer. Det liksom funkar i alla fall. Den resulterande känslan är luftigare och smidigare; vägen från problem A till lösning B kantas inte av typrelaterade kompileringsfel.
En van användare av C++ eller något annat statiskt typat språk kan mycket väl fråga sig hur de dynamiska språken över huvud taget kan fungera:
Hur vet kompilatorn hur mycket minne den ska allokera om den inte vet vilken typ variabeln ska ha
Vad händer om man försöker lägga en sträng i `sum` senare?
Vad händer om man försöker göra något med en `int` som man i själva verket bara kan göra med strängar, eller arrayer?
Svaret är att kompileringsprocessen mestadels håller sig utanför sådana frågor i dynamiska språk. Variabeln `sum` i sig _har_ inte någon typ. Det är bara värdet (0) som har en typ. Vilket svarar på frågan om vad som skulle hända om man lade en sträng eller något annat i `sum` senare i programmet i ett dynamiskt språk: ingenting särskilt. Det fortsätter att fungera.
På samma sätt fungerar det att inte veta vid kompilering hur mycket minne en variabel kommer att behöva. Allt det hanteras istället under körning. Resultatet blir att vi inte är helt optimala vad gäller prestanda och minnesåtgång i dynamiska språk. Men det betraktas som en acceptabel tradeoff mot att programmet är lättare att skriva och ändra.
Slutligen, om man försöker behandla värdet 0 som en sträng eller en array eller liknande: de två saker som kan hända är
1. att värdet 0 konverteras implicit till rätt typ innan operationen utförs, eller
2. att ett fel meddelas vid den punkten under körning.
(Vilken av dessa som händer beror på exakt vilka typer som är inblandade, samt hur tillåtande språket är.) I ingetdera fall får vi en tidig varning under kompilering; vi måste vänta tills någonting faktiskt går fel i programmet för att vi ska märka det.
Det är den här sista punkten jag har tänkt på mycket på sistone. Förespråkare för de statiskt typade språken kan omedelbart peka på detta som en form av vansinne: varför skulle man vilja få fel vid körning som man skulle kunna få vid kompilering? Varför skulle man välja att ens starta ett program som var felaktigt på det sättet? (“Skjut aldrig upp till körning vad du kan göra vid kompilering”…)
Man kan ju svara att program som skrivs i de dynamiska språken ofta är rätt småskaliga: de är inte så långa i antalet rader mätt, och de tar inte så lång tid att köra. Så alla eventuella fel man gör på vägen upptäcker man också rätt snabbt.
Även om det antagandet ofta stämmer, så tror jag att det allt oftare är fel. Idag skrivs storskaliga, ambitiösa, affärskritiska program även i dynamiska språk. Ta PHP hos Facebook som exempel. Eller JavaScript hos i princip vem som helst med frontend-kod. Det är två exempel på språk som gradvis har funnit sig i betydligt större skor än vad de ursprungligen skapades för. När saker skalar upp så blir avsaknaden av statisk typning allt mer kännbar.
Något spännande har dock hänt de senaste åren. Facebook har lanserat Hack, en statiskt typad version av PHP. Microsoft har släppt TypeScript, en statiskt typad version av JavaScript. Båda de här insatserna vinner framgång genom att *behålla* de dynamiska språken som redan har vunnit popularitet, men att i efterhand förse dem med statisk typning. Ofta kan man skriva sin kod som man brukade göra, men där man vill kan man ange en statisk typ, och då kontrolleras den vid kompilering. Det är på många sätt den bästa av två världar: ett dynamiskt språk med statiska drag.
Även Python har på sistone fått statisk typning, via PEP 484 (ett förslag om “Type Hints” i språket) och det externa verktyget `mypy`. Men känslan är lite annorlunda än Hack och TypeScript — PEP 484 är tydlig med att det _inte_ handlar om att få typfel vid kompilering, utan att det är en sorts API-dokumentation. Riktlinjer, snarare än hårda regler. I slutänden får marknaden avgöra om man vill skriva Python med eller utan typer. Jag ser fram emot att prova med, och känna hur det känns.
Jag gillar tanken på att kunna ha ett dynamiskt språk som inte är i vägen när man skriver små saker, men som också klarar av att stötta upp en med statisk typkontroll när man behöver struktur och skala.
Typer i programmering har en intressant historia. Tre till synes oberoende definitioner har flödat samman till ett enda koncept:
· Typ som i “datatyp”, precis som i `int sum` ovan: representationen av ett värde i minnet. Det bruket går tillbaka till Algol på 60-talet, och är vad de flesta idag menar med ordet. Innan kunde man se ordet “typ” användas på ett vardagligt sätt; i och med Algol börjar det betyda “representation”.
· Typ som i “algebraisk typ” eller “typalgebra”: insikten att typer är värden som kan kombineras och manipuleras på olika sätt. `struct` i C är en typisk produkttyp, till exempel, men även `Tuple` i många språk. Det finns typer som `Either` och `Option` som indikerar val av olika slag. Den största påverkan den här traditionen har haft på industrin är nog dock generiska typer: om man har en `String` så kan man också bilda en `List<String>` eller en `Promise<String>`.
· Typ som i “typteori”. På tidigt 1900-tal gick matematiken igenom en kris i sina grundvalar. Bertrand Russell och andra undersökte sätt att undvika paradoxer i formella logiska system; typer blev en möjlig mekanism. Långt senare visade det sig att det finns en bro som överför alla resultat i logik till motsvarande resultat i programmering, och vice versa. Det som man hade kallat typer i matematik är precis det som man kallar typer i programmering.
Vi som industri har rätt bra koll på den första definitionen av typ. Vi får allt bättre förståelse av den andra definitionen. Den tredje har fortfarande inte börjat lämna spår i konventionella språk, men det känns som att det är i den riktningen vi rör oss.
Vi vet inte hur de kommande årtiondens programspråk kommer att se ut, men min gissning är att de kommer att utnyttja statisk typning på alltmer intressanta sätt. Man får mersmak av att skriva program som gör rätt sak första gången man kör dem.
Här hittar du hela Informators kursutbud inom Webbutveckling