Tyyppijärjestelmä

Ohjelmointikielen **tyyppijärjestelmä** määrittää, kuinka ohjelmointikieli luokittelee ohjelmassa esiintyvät entiteetit **tyyppeihin**. Tyyppejä ovat lausekkeet, muuttujat ja niin edelleen. Tyyppijärjestelmä vaikuttaa kuinka kielessä käsitellään tyyppejä ja tyyppien välisiä toimintoja.

Tyyppi tai **tietotyyppi** on numeeristen arvojen tulkintatapa. Tyyppi voi koostua joukosta sallittuja arvoja (enumeraatio). Tyyppi voi olla komposiittityyppi, joka sisältää useita arvoja (kuten taulukko, luokka ja niin edelleen).

Tyyppijärjestelmä voi olla laajennettava: ohjelmoija voi määrittää uusia tyyppejä kuten luokkia.

## Merkitys

Tietotyypit antavat tulkinnan tietokoneen muistissa oleville bittijonoille. Tavanomaisten tietokoneiden muisti koostuu järjestyksessä olevista biteistä, joiden tulkinta on vapaa. Niinpä sama osa muistin sisältöä voidaan tulkita vaikkapa merkiksi, kokonaisluvuksi, liukuluvuksi tai osoitteeksi toiseen muistipaikkaan. Tyypit kertovat ohjelmille ja ohjelmoijille, kuinka nämä bitit tulkitaan.

Muuttujan tai arvon tyyppi määrittelee, millä tavoin se voi toimia muiden muuttujien tai arvojen kanssa. **Tyyppitarkastuksessa** varmistutaan siitä, että tyypit toimivat näiden rajoitusten mukaan, eli tarkastetaan osa ohjelman semantiikasta. Tyyppitarkastus voidaan suorittaa ohjelman käännösaikana (*staattinen tyyppitarkastus*) tai ajoaikana (*dynaaminen tyyppitarkastus*).

Ohjelmointikielissä voidaan antaa lause:

```c++
int abc = 42;
```

.. jossa `int` on muuttujan `abc` *tyyppi* ja *literaali* `42` on muuttujaan sijoitettava arvo.
Kääntäjä antaa virheen, jos muuttujaan yritetään sijoittaa jotain mitä tyyppijärjestelmän mukaan siihen ei voida sijoittaa kuten:

```c++
vector<int> v;
abc = v; // VIRHE
```


Tyyppijärjestelmä havaitsee jos tyypillä yritetään tehdä toimintoja, joita sillä ei voida tehdä:

```c++
cout << abc + abc; // ok
cout << *abc; // VIRHE
```


Tyyppijärjestelmä antaa seuraavia merkittäviä hyötyjä:

* **Tyyppiturvallisuus** - Tyyppien ansiosta ohjelmointikielen kääntäjä tai tulkki voi tunnistaa virheitä ohjelmakoodissa. Esimerkiksi lauseke 
 "Hello, World" / 3
voidaan tunnistaa virheelliseksi, koska merkkijonon jakaminen kokonaisluvulla ei ole mielekäs operaatio.
* **Optimointi** - Jos kääntäjä esimerkiksi tietää, että jokin muuttuja on aina 8-bittinen kokonaisluku, sen ei tarvitse varata muistista arvoa varten kahdeksaa bittiä enempää tilaa. Kääntäjä voi myös käyttää 8-bittisille kokonaisluvuille parhaiten soveltuvia konekielen käskyjä muuttujan käsittelyyn.
* **Dokumentointi** - Ohjelmoija voi tyyppien huolellisella määrittelyllä ja käytöllä selventää ohjelmakoodin tarkoitusta. Tyyppijärjestelmä voi esimerkiksi mahdollistaa rahamäärää kuvaavien arvojen erottamisen paljaista reaaliluvuista. Kirjoittamalla esimerkiksi funktion paluutyypiksi rahamäärän eikä reaalilukua ohjelmoija dokumentoi osan funktion merkityksestä.
* **Abstraktointi** - Tyyppien ansiosta ohjelmaa on helpompi tarkastella tietokoneen toimintojen tasoa korkeammalla abstraktiotasolla. Esimerkiksi pankkisovelluksen parissa työskentelevä ohjelmoija voi käyttää ohjelmoinnissaan ja ajattelussaan enemmän pankkialan termistöä ja vähemmän tietokoneen teknisten yksityiskohtien termistöä.

Ensimmäisen luokan tyyppejä ovat helposti käytettävät muuttujatyypit kuten `int` C++-kielessä. Ensimmäisen luokan tyypeillä:
* voidaan ajonaikana luoda helposti uusia arvoja olemassa olevista arvoista
* arvoja voidaan välittää argumentteina funktioille tai palautettua funktioista
* arvoja voidaan tallettaa säiliöluokkiin

Eräät kielet tukevat funktioita ensimmäisen luokan tyyppeinä: näitä ovat muun muassa Haskell, Scheme, Python ja Lua.

Yksittäisen rajapinnan tarjoamista erityyppisille entiteeteille kutsutaan polymorfismiksi.

## Staattinen vs. dynaaminen tyypitys

Tyyppijärjestelmä voi olla staattinen tai dynaaminen: staattisessa tyypityksessä tyyppisidos päätetään käännösvaiheessa kun taas dynaamisessa tyypityksessä tyyppi tarkistetaan ja päätetään ajonaikaisesti.

Esimerkiksi C-kielen tyyppijärjestelmä on staattinen, koska muuttujien tyypit on pakko antaa muuttuja- ja funktioesittelyiden yhteydessä. Sen sijaan useimmat tulkattavat (eivät kuitenkaan kaikki) kielet ovat dynaamisesti tyypittäviä kieliä, kuten Perl ja Python. Java käyttää yhdistelmää käännösaikaisia ja ajonaikaisia tarkistuksia.

Esimerkiksi seuraava ohjelmakoodinpätkä on laillista Pythonia:


```python
def parse_arg(index):
     count, filename = ARGV[0], ARGV[1]
     count = int(count)
     return count, filename
```


Aluksi `count` on tässä merkkijonotyyppinen, koska taulukon `ARGV` (komentoriviargumentit) alkiot ovat merkkijonoja. Seuraavalla rivillä `count` muunnetaan kokonaislukutyyppiseksi ennen `return` -lausetta. Muuttujan `count` tyyppi siis vaihtuu ajonaikana, mutta siitä ei aiheudu virhettä.

Dynaaminen tyypitys tarjoaa joustavuutta, mutta se ei ole automaattisesti staattista tyypitystä parempi ratkaisu. Staattisen tyyppijärjestelmän avulla monet virheet voidaan havaita käännösaikana, kun taas dynaamisesti tyypitetyssä kielessä vastaava virhe havaitaan vasta ajon aikana.

Joissakin kielissä tyyppi voidaan päätellä, jolloin se näyttää dynaamiselta mutta on todellisuudessa staattisesti tyypitetty:

```c
var x = 10; // C#
auto x = 10; // C++

// molemmissa vastaa samaa kuin:
int x = 10;
```


OCaml käyttää *implisiittistä* tyypitystä, joka kuitenkin on staattisesti tyypitetty.

## Manifesti vs. implisiittinen tyypitys
Kielissä, joissa tyyppi määritetään *eksplisiittisesti* käytetään manifestityypitystä ja tyyppiannotaatiota. Esimerkiksi:

```cpp
double sq47(double n)
{
    double result = 4.7 * n * n;
    return result;
}
```


Muutoin käytössä on implisiittinen tyypitys, jolloin tyyppi voidaan päätellä. Dynaamisesti tyypitetyt kielet käyttävät yleensä implisiittistä tyypistystä.
Esimerkki implisiittisestä tyypityksestä Haskell-kielellä, joka käyttää staattista tyypitystä:

```haskell
sq47 n = result where
    result = 4.7 * n * n
```


Joissakin tapauksissa dynaaminen ja implisiittinen voivat näyttää samoilta, mutta käytännössä ovat eri asioita.

## Nimellinen vs. rakenteellinen
Kielet voivat käyttää nimettyä tyyppiä huolimatta sen rakenteesta, jolloin tyyppi tarkistetaan vain sille annetun nimen perusteella. Esimerkiksi ohjelmoijan luomat tyypit:

```c
struct A {
    int h;
    int m;
};

struct B {
    int h;
    int m;
};
```

.. jossa `A` ja `B` ovat eri tyyppejä.

Nimelliseen tyyppiin on löyhempiä tapauksia, kuten perityn luokan välittäminen sen kantaluokan mukaan.

Rakenteellisessa tyypityksessä (kutsutaan myös *ankkatyypitykseksi*) tyyppejä voidaan käsitellä keskenään vaihdettavina mikäli niillä on sama rakenne ja ne tukevat samoja toimintoja.

Esimerkiksi C++-kielellä:

```c++
template <typename T, typename U>
T addEm(T a, U b)
{
    return a + b;
}
```


Python-kielellä:

```python
def addEm(a, b):
    return a + b
```


Go-kielellä:

```go
type Shape interface {
        Area() float64
}
```


## Latentti tyypitys
Latentti tyypitys on ohjelmointikielien kuten Schemen tyypitys, jossa yleisesti ottaen et voi ohjelman lähdekoodia lukemalla päätellä mitä tyyppiä tiettyyn muuttujaan tai lausekkeeseen liittyvä data on.

## Vahva ja heikko tyypitys

Vahva ja heikko tyypitys on tyyppijärjestelmän ominaisuus, joka määrittelee miten erityyppisten muuttujien väliset muunnokset käsitellään keskenään. Kieli voi käyttää joko vahvaa tai heikkoa tyypitystä.

## Tyyppijärjestelmättömät ohjelmointikielet
Eräissä ohjelmointikielissä ei ole tyyppijärjestelmää: esimerkkejä ovat BCPL ja B-kielet.

Forth-kieli erottelee kokonaisluvut ja liukuluvut, joten sillä on tyypin käsite, mutta näitä käsitellään eri syntaksilla ja sillä ei ole tyyppitarkistusta.

## Katso myös
* Polymorfismi
* Myöhäinen ja varhainen sidonta

## Lähteet

## Kirjallisuutta
* 

## Aiheesta muualla
* Type Theory