NEVER - mistä on kyse TypeScriptin oudoimmassa tyypissä? Osa 1/2

lokak. 12, 2023

NEVER - mistä on kyse TypeScriptin oudoimmassa tyypissä?

Tämä kirjoitus on tarkoitettu TypeScriptiä jo jonkin verran tunteville. Lähestymistapa on opetuksellinen ja käymme blogissa asioita läpi esimerkkien avulla. 


Mitään blogin koodia ei tarvitse itse suorittaa, joskin se voisi olla oppimisen kannalta hyödyllistä. Blogin koodiesimerkit on testattu ts-node:lla (ts-node:n v10.9.1), joka on TypeScriptiä lennosta tulkkaava node-client, jonka voit asentaa myös omalle koneellesi. Käynnistä tulkki komentoriviltä komennolla ts-node ja sulje tulkki komennolla .exit .


Tässä blogikirjoituksessa esittelen, mistä never-tyypissä on oikein kysymys, mihin sitä tarvitaan ja missä yhteyksissä tyyppi esiintyy. Saat blogista neverin lisäksi irti paljon muutakin TypeScript-asiaa, joten hypi vapaasti niihin osiin, jotka tuntuvat kiinnostavilta ja hyödyllisiltä.


Johan Stenroth

Webscale Oy

10.10.2023 Espoo

TypeScript tuo tyypitykset JavaScriptiin

TypeScript on Microsoftin kehittämä JavaScript-laajennus, jonka keskeisin hyöty on tarjota vahva tyypitys koodin kehitys- ja käännösvaiheeseen, jotta ajonaikaiset tyypityksiin liittyvät ongelmat tulisivat etukäteen minimoiduiksi.


TypeScriptillä on tyyppi jokaiselle kahdeksalle JavaScriptissä määritellylle primitiivityypille, jotka ovat string, number, bigint, boolean, object, null, undefined ja symbol. TypeScript-tyypit tukevat myös tuttuja JavaScript-rakenteita, kuten listoja, objekteja ja Map-rakennetta. Lisäksi TypeScript tukee erikoisempia tyyppitapauksia kuten any, unknown ja never.


TypeScript on ennen kaikkea apuväline paremman JavaScript-koodin tuottamiseen ja vain hyvin rajatuissa käyttötapauksissa TypeScript-koodista jää mitään jäljelle käännettyyn JavaScript -koodiin.


Never on outo tyyppi

Never on puolestaan TypeScriptin oma primitiivityyppi, joka tuotiin kieleen version 2.0 mukana. Tätä kirjoitettaessa TypeScript on saavuttanut version 5.2.


Never-tyypillä on oma merkityksensä muuttujien ja funktioiden paluuarvojen tyyppinä. Muuttujien yhteydessä never edustaa sellaista koodin osaa, jota ei voida koskaan saavuttaa. Funktioiden paluuarvojen yhteydessä puolestaan never edustaa funktiota, jota ei voida koskaan suorittaa loppuun saakka. Perehdymme never-tyyppisten muuttujien ja funktion paluuarvojen ominaisuuksiin tarkemmin myöhemmin blogissa.


Never on TypeScript-maailman outo tyyppi, joka edustaa jokaisen muun tyypin alityyppiä, mutta ainoastaan never itse voi olla never-tyypin alityyppi. Tästä pian lisää tyyppien laajenteita käsittelevän otsikon alla.


Kehittäjän tarvitsee vain harvoin kirjoittaa never-tyyppi itse koodiin. Sen sijaan, useimmiten never-tyyppi on vain TypeScript-tulkin tekemä lopputoteamus siitä, ettei muuttujaa käsittelevä koodilohko ole TypeScript-kääntäjän ymmärryksen mukaan saavutettavissa. Tämä voi esimerkiksi auttaa kehittäjää huomaamaan koodissa osia, johon suorittavan koodin on loogisesti mahdotonta päästä.


Kaikki TypeScript-tyypit ovat neverin laajenteita

Joukko {‘A’} on joukon {‘A’, ‘B’, ‘C’} osajoukko. Näin ollen joukon {‘A’, ‘B’, ‘C’} voidaan sanoa laajentavan (engl. extend) joukkoa {‘A’}, koska joukon {‘A’} kaikki alkiot sisältyvät joukkoon {‘A’, ‘B’, ‘C’}. Identtiset joukot ovat osajoukkoja itselleen, joten esimerkiksi joukko {‘A’} on joukon {‘A’} osajoukko. 


Samalla tavalla tyyppien maailmassa voidaan esimerkiksi sanoa tyypin string laajentavan literaalityyppiä “foo”, koska tyyppi string sisältää kaikki mahdolliset merkkijonovariaatiot.


Never puolestaan sisältyy vaivihkaa kaikkiin muihin tyyppeihin, koska kaikki tyypit ovat never-tyypin laajennuksia. Toisin sanoen, TypeScript-maailmassa kaikki tyypit laajentavat never-tyyppiä. Myös never itse laajentaa itseään. Never on vähän kuin tyhjä joukko joukko-opissa, sillä tyhjä joukko on kaikkien muiden joukkojen osajoukko.


TypeScript-maailmassa operoidaan joukko-opillisilla konsepteilla. Esimerkiksi, jos haluamme kirjoittaa rajapinnan (engl. interface), joka laajentaa jo olemassa olevaa rajapintaa, voimme kirjoittaa sen käyttäen TypeScriptin extends-ilmaisua.

Muutama hyödyllinen TypeScript-konsepti

Type Narrowing & Type Guard

Sen lisäksi, että TypeScript-tulkki osaa tulkita JavaScripin ajonaikaisia staattisia tyyppejä, se osaa tulkita myös koodin suoritusta ohjaavia rakenteita, kuten if/else, ternary-operaattori, toistorakenteita ja totuustarkistusoperaattoreita.


Näissä koodin ohjausrakenteissa mahdollisten tyyppien kirjo saattaa supistua (engl. type narrowing). Tyyppien kirjon supistuminen tapahtuu ns. tyyppiä vartioivassa osassa (engl. type guard).


TypeScript ymmärtää useita Type Narrowing -rakenteita. Yksi yleinen rakenne on JavaScriptin typeof-operaattori.


Esimerkkikoodin padding-muuttuja voi olla tyyppiä number tai string. Repeat-metodi on olemassa ainoastaan number-tyyppisille muuttujille, mutta se on TS-tulkille okei, sillä if-lauseen type guard varmistaa, että if-blokin sisällä padding-muuttuja on aina number-tyyppiä.


Type predicate function

Joskus type guardin logiikaksi tarvitaan monimutkaisempaa koodia, jota TypeScript ei kykene automaattisesti päättelemään. Tällaisessa tapauksessa voimme luoda oman funktion tekemään päätelmä ja kertoa TypeScriptille, minkä type guard -päättelyn funktio toteuttaa. Tällainen funktio on nimeltään Type Predicate Function.


Kehittäjän on oltava tarkkana, jotta funktio todella tekee juuri oikean tyyppitarkistuksen, sillä kuten todettua, TypeScript ei funktion sisäistä logiikkaa kykene tarkastamaan, vaan uskoo täysin sen, minkä tyyppitarkastuksen kehittäjä väittää TypeScriptille funktion toteuttavan.


Type Predicate Function palauttaa aina
boolean-arvon ja tyypin tarkistuksen tiedot välitetään TypeScriptille funktion palautustyypin merkinnän yhteydessä, johon ei tällä kertaa merkitäkään boolean, vaan muoto “[funktion parametrin nimi] is [tyypin nimi]”. Katso esimerkki alta.

Funktion logiikassa tarkastetaan, onko pet-muuttujalla property swim. Jos swim-property on olemassa, tiedämme pet-muuttujan olevan tyyppiä Fish ja funktio palauttaa boolean-arvon true. Nyt käyttämällä funktiota koodissa, TypeScript osaa suhtautua funktioon Type Guardina.


Exhaustiveness checking & never

Switch-case-rakenteessa on mahdollista toteuttaa never-tyypin avulla union-tyypin perinpohjainen alityyppien läpikäynnin tarkastus (engl. exhaustiveness checking). 


Tiedämme jo useita tapoja miten union-tyyppisten muuttujien jäljellä olevia tyyppivaihtoehtoja voidaan supistaa (engl. narrow) type guardien avulla esimerkiksi typeof-operaattorilla tai Type Predicate Functionin avulla.


Kun haluamme kuitenkin erikseen varmistaa TypeScriptin avulla, että onhan kaikki tyyppivaihtoehdot nyt läpi koluttu, voimme tehdä tämän tarkistuksen merkitsemällä muuttujan
never-tyypiksi. Jos TypeScript hyväksyy never-tyypin, unionilla ei ole enää yhtäkään tyyppivaihtoehtoa jäljellä ja muuttujan tyypit on käsitelty Type Guardeille läpikotaisin, eli sille on toteutettu “exhaustive type checking”. Katso esimerkki alta.

Koska TypeScript hyväksyy never-tyypin, tyypin exhaustiveness (tai ts. tyypin läpikotaisuustarkistus) on onnistunut, sillä myös TypeScript on sitä mieltä, ettei Shape-tyypin unionilla ole default-blokin sisällä enää yhtäkään mahdollista tyyppiä jäljellä, jota aiemmat type guardit (rivit 5 ja 7) eivät olisi jo poissulkeneet.


Never-tyyppistä arvoa ei ole olemassa

Muuttujan tyypiksi on sallittua asettaa never, mutta never-tyyppiselle muuttujalle emme voi asettaa mitään arvoa.

Mikään arvo, kuten esimerkiksi undefined, ei ole sallittu arvo never-tyyppiselle muuttujalle. Never on puhdas TypeScript-maailman tyyppi ja konsepti, eikä JavaScript-maailmassa ole never-tyyppisiä arvoja.



Kiitos kun tutustuit never-blogin ensimmäiseen osaan. Seuraavassa osassa (osa 2/2) keskitymme neverin rooliin tyyppisupistimissa (engl. type guard), unioneissa ja leikkauksissa. Lisäksi katsomme, mikä merkitys neverillä on funktioiden paluuarvon tyyppinä.


Nähdään toisessa osassa!

Viimeisimmät kirjoitukset

Webscalen konsultteja.
03 May, 2024
Kysy konsultilta -blogisarjassa konsulttimme tekevät selkoa alan termeistä ja ilmiöistä. Vastaukset on mitoitettu sopimaan pieneenkin tiedonnälkään. Tällä kertaa selvitämme, miten FinOps auttaa pilvikustannuksissa?
Webscalen konsultteja.
26 Apr, 2024
Kysy konsultilta -blogisarjassa konsulttimme tekevät selkoa alan termeistä ja ilmiöistä. Vastaukset on mitoitettu sopimaan pieneenkin tiedonnälkään. Tällä kertaa selvitämme, mikä on AWS Landing Zone?
Webscalen konsultteja.
19 Apr, 2024
Kysy konsultilta -blogisarjassa konsulttimme tekevät selkoa alan termeistä ja ilmiöistä. Vastaukset on mitoitettu sopimaan pieneenkin tiedonnälkään. Tällä kertaa selvitämme, mitä on DevSecOps?
Webscalen konsultteja.
12 Apr, 2024
Kysy konsultilta -blogisarjassa konsulttimme tekevät selkoa alan termeistä ja ilmiöistä. Vastaukset on mitoitettu sopimaan pieneenkin tiedonnälkään. Tällä kertaa selvitämme, mikä on Serverless Framework?
Lisää kirjoituksia
Share by: