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.
Carl
Mäsak
A developer with a fondness for
dynamic languages, evolutionary design, and meaningful unit testing. Likes to
heal ailing legacy applications. Designs and implements programming languages
in his spare time.
Skicka en kommentar
Trevligt att du vill dela med dig av dina åsikter! Tänk på att hålla på "Netiketten" och använda vårdat språk.