Maarit Harsu. O h j e l m o i n t i k i e l e t Periaatteet, käsitteet, valintaperusteet



Samankaltaiset tiedostot
1. Olio-ohjelmointi 1.1

815338A Ohjelmointikielten periaatteet

Ongelma(t): Miten jollakin korkeamman tason ohjelmointikielellä esitetty algoritmi saadaan suoritettua mikro-ohjelmoitavalla tietokoneella ja siinä

Concurrency - Rinnakkaisuus. Group: 9 Joni Laine Juho Vähätalo

4. Lausekielinen ohjelmointi 4.1

Imperatiivisten ohjelmien organisointiparadigmojen. historia

Imperatiivisten ohjelmien organisointiparadigmojen historia

Johdanto Meta Kielten jaotteluja Historia. Aloitusluento. TIES542 Ohjelmointikielten periaatteet, kevät Antti-Juhani Kaijanaho

815338A Ohjelmointikielten periaatteet

Prolog kielenä Periaatteet Yhteenveto. Prolog. Toni ja Laura Fadjukoff. 9. joulukuuta 2010

Algoritmit 1. Luento 3 Ti Timo Männikkö

TIE PRINCIPLES OF PROGRAMMING LANGUAGES Eiffel-ohjelmointikieli

4. Lausekielinen ohjelmointi 4.1

ELM GROUP 04. Teemu Laakso Henrik Talarmo

TIEA255 Tietotekniikan teemaseminaari ohjelmointikielet ja kehitysalustat. Antti-Juhani Kaijanaho. 16. helmikuuta 2011

Tietorakenteet ja algoritmit - syksy

11/20: Konepelti auki

812341A Olio-ohjelmointi, I Johdanto

Ohjelmoinnista. Ohjelmien toteutukseen tarjolla erilaisia välineitä:

Tietorakenteet ja algoritmit Johdanto Lauri Malmi / Ari Korhonen

Käännös, linkitys ja lataus

ADA. Ohjelmointikieli. Ryhmä 5 Henna Olli, Päivi Hietanen

Tietotekniikan valintakoe

Rinnakkaisuuden hyväksikäyttö peleissä. Paula Kemppi

Tutoriaaliläsnäoloista

AS C-ohjelmoinnin peruskurssi 2013: C-kieli käytännössä ja erot Pythoniin

815338A Ohjelmointikielten periaatteet Harjoitus 5 Vastaukset

811120P Diskreetit rakenteet

TIEA241 Automaatit ja kieliopit, syksy Antti-Juhani Kaijanaho. 30. marraskuuta 2015

5. HelloWorld-ohjelma 5.1

Tietueet. Tietueiden määrittely

Ohjelmoinnin perusteet Y Python

815338A Ohjelmointikielten periaatteet

811120P Diskreetit rakenteet

Ongelma(t): Miten mikro-ohjelmoitavaa tietokonetta voisi ohjelmoida kirjoittamatta binääristä (mikro)koodia? Voisiko samalla algoritmin esitystavalla

Ohjelmointikieli TIE Principles of Programming Languages Syksy 2017 Ryhmä 19

tään painetussa ja käsin kirjoitetussa materiaalissa usein pienillä kreikkalaisilla

Osoitin ja viittaus C++:ssa

Ruby. Tampere University of Technology Department of Pervasive Computing TIE Principles of Programming Languages

812347A Olio-ohjelmointi, 2015 syksy 2. vsk. II Johdanto olio-ohjelmointiin

815338A Ohjelmointikielten periaatteet Harjoitus 6 Vastaukset

11.4. Context-free kielet 1 / 17

PERL. TIE Principles of Programming Languages. Ryhmä 4: Joonas Lång & Jasmin Laitamäki

Chapel. TIE Ryhmä 91. Joonas Eloranta Lari Valtonen

815338A Ohjelmointikielten periaatteet Harjoitus 4 vastaukset

Ohjelmointi 1 / syksy /20: IDE

Lisää pysähtymisaiheisia ongelmia

7/20: Paketti kasassa ensimmäistä kertaa

Rinnakkaisuus. parallel tietokoneissa rinnakkaisia laskentayksiköitä concurrent asioita tapahtuu yhtaikaa. TTY Ohjelmistotekniikka

815338A Ohjelmointikielten periaatteet Harjoitus 3 vastaukset

.NET ajoympäristö. Juha Järvensivu 2007

Perinteiset tietokoneohjelmat alkavat pääohjelmasta, c:ssä main(), jossa edetään rivi riviltä ja käsky käskyltä.

815338A Ohjelmointikielten periaatteet Harjoitus 7 Vastaukset

TT00AA Ohjelmoinnin jatko (TT10S1ECD)

Ohjelmointi 1. Kumppanit

Koottu lause; { ja } -merkkien väliin kirjoitetut lauseet muodostavat lohkon, jonka sisällä lauseet suoritetaan peräkkäin.

Olio-ohjelmointi Johdanto olio-ohjelmointiin

Alkuarvot ja tyyppimuunnokset (1/5) Alkuarvot ja tyyppimuunnokset (2/5) Alkuarvot ja tyyppimuunnokset (3/5)

815338A Ohjelmointikielten periaatteet Harjoitus 2 vastaukset

2. Lisää Java-ohjelmoinnin alkeita. Muuttuja ja viittausmuuttuja (1/4) Muuttuja ja viittausmuuttuja (2/4)

C-ohjelmoinnin peruskurssi. Pasi Sarolahti

TIE Principles of Programming Languages. Seminaariesityksen essee. Ryhmä 18: Heidi Vulli, Joni Heikkilä

17/20: Keittokirja IV

Ohjelmoinnin peruskurssien laaja oppimäärä

Pythonin alkeet Syksy 2010 Pythonin perusteet: Ohjelmointi, skriptaus ja Python

Ohjelmistojen mallintaminen, mallintaminen ja UML

Clojure, funktionaalinen Lisp murre

Oppimistavoitteet kurssilla Rinnakkaisohjelmointi

19/20: Ikkuna olio-ohjelmoinnin maailmaan

Algoritmit. Ohjelman tekemisen hahmottamisessa käytetään

Zeon PDF Driver Trial

ATK tähtitieteessä. Osa 3 - IDL proseduurit ja rakenteet. 18. syyskuuta 2014

Ohjelmointikielten kehityshistoriaa

Johdatus ohjelmointiin

15. Ohjelmoinnin tekniikkaa 15.1

Ohjelmoinnin perusteet Y Python

IDL - proseduurit. ATK tähtitieteessä. IDL - proseduurit

Johdatus Ohjelmointiin

Varhaiset oliokielet Modula, CLU ja Smalltalk. T : Seminar on the History of Programming Languages Kari Koskinen Otaniemi 29.9.

Käyttöjärjestelmien historia. Joni Herttuainen Henri Jantunen Markus Maijanen Timo Saksholm Johanna Tjäder Eetu Turunen

5. HelloWorld-ohjelma 5.1

Ohjelmoinnin peruskurssien laaja oppimäärä

12. Javan toistorakenteet 12.1

4. Luokan testaus ja käyttö olion kautta 4.1

15. Ohjelmoinnin tekniikkaa 15.1

Rekursiolause. Laskennan teorian opintopiiri. Sebastian Björkqvist. 23. helmikuuta Tiivistelmä

Ehto- ja toistolauseet

P e d a c o d e ohjelmointikoulutus verkossa

TIE Principles of Programming Languages CEYLON

ITKP102 Ohjelmointi 1 (6 op)

Oliosuunnitteluesimerkki: Yrityksen palkanlaskentajärjestelmä

Tähtitieteen käytännön menetelmiä Kevät 2009 Luento 5: Python

12. Javan toistorakenteet 12.1

Kielioppia: toisin kuin Javassa

Ohjelmoinnin peruskurssien laaja oppimäärä

Ohjelmoinnin peruskurssien laaja oppimäärä

Imperatiivisen ohjelmoinnin peruskäsitteet. Meidän käyttämän pseudokielen lauseiden syntaksi

D-OHJELMOINTIKIELI. AA-kerho, 33. Antti Uusimäki. Arto Savolainen

Sisällys. 3. Muuttujat ja operaatiot. Muuttujat ja operaatiot. Muuttujat. Operaatiot. Imperatiivinen laskenta. Muuttujat. Esimerkkejä: Operaattorit.

Aliohjelmatyypit (2) Jakso 4 Aliohjelmien toteutus

Transkriptio:

Maarit Harsu O h j e l m o i n t i k i e l e t Periaatteet, käsitteet, valintaperusteet 6. elokuuta 2012

c 2012 Maarit Harsu Tämän Ohjelmointikielet-teoksen käyttöoikeutta koskee Creative Commons Nimeä- Ei muutoksia-epäkaupallinen 1.0 Suomi -lisenssi. Nimeä Teoksen tekijä on ilmoitettava siten kuin tekijä tai teoksen lisensoija on sen määrännyt (mutta ei siten että ilmoitus viittaisi lisenssinantajan tukevan lisenssinsaajaa tai Teoksen käyttätapaa). Ei muutettuja teoksia Teosta ei saa muuttaa, muunnella tai käyttää toisen teoksen pohjana. Epäkaupallinen Lisenssi ei salli teoksen käyttöä ansiotarkoituksessa. Lisenssi on nähtävillä kokonaisuudessaan osoitteessa http://creativecommons.org/licenses/by-nd-nc/1.0/fi/

Sisältö Alkusanat 9 1 Johdanto 11 1.1 Taustaa............................ 11 1.2 Ohjelmointiparadigmat.................... 13 1.3 Ohjelmointikielten historiaa................. 15 1.4 Ohjelmointikielten sukupolvet................ 20 1.5 Esimerkki ohjelmointikielten kehittymisestä......... 21 1.6 Ohjelmointikielen suunnittelu................ 22 1.7 Ohjelmointikielen toteuttaminen............... 26 2 Syntaksi ja semantiikka 31 2.1 Taustaa............................ 31 2.2 Leksikaalianalyysi...................... 32 2.3 Kontekstittomat kieliopit................... 34 2.4 Syntaksianalyysi....................... 41 2.4.1 LL-jäsennys...................... 41 2.4.2 LR-jäsennys..................... 46 2.5 Semanttinen analyysi..................... 48 2.6 Attribuuttikieliopit...................... 50 3 Tietoalkiot ja niiden sidonnat 55 3.1 Taustaa............................ 55 3.2 Sijoituslause.......................... 56 3.3 Lausekkeet ja niiden evaluointi................ 58 3.4 Sidonnat............................ 60 3.5 Elinajat............................ 62 3.6 Näkyvyydet.......................... 64 3

4 SISÄLTÖ 4 Tyypit 69 4.1 Tyyppien merkitys...................... 69 4.2 Tyyppien suunnittelunäkökohtia............... 70 4.3 Vahva tyypitys......................... 72 4.4 Tyyppien jaottelu....................... 73 4.5 Skalaarityypit......................... 74 4.5.1 Numeeriset tyypit................... 74 4.5.2 Loogiset tyypit.................... 75 4.5.3 Merkkityypit..................... 75 4.5.4 Luetellut tyypit.................... 76 4.5.5 Osavälityypit, alityypit ja johdetut tyypit...... 77 4.6 Rakenteiset tyypit....................... 78 4.6.1 Taulukkotyypit.................... 78 4.6.2 Taulukon toteuttaminen................ 81 4.6.3 Merkkijonotyypit................... 82 4.6.4 Taulukkotyypin indeksirajat............. 84 4.6.5 Tietuetyypit...................... 86 4.7 Osoitintyypit......................... 89 4.7.1 Osoittimien ominaisuuksia.............. 89 4.7.2 Osoittimien ongelmia................. 90 4.7.3 Osoitinongelmien ratkaisuja............. 91 4.7.4 Viitetyypit...................... 93 4.8 Joukkotyypit......................... 94 4.9 Tyyppien ekvivalenssi..................... 95 5 Lauseet 99 5.1 Taustaa............................ 99 5.2 Koottu lause.......................... 100 5.3 Valintalauseet......................... 101 5.3.1 Kaksisuuntainen valintalause............. 101 5.3.2 Monivalintalause................... 103 5.4 Toistolauseet......................... 105 5.4.1 Määrätty toistolause................. 105 5.4.2 Määräämätön toistolause............... 107 5.5 Vartioidut komennot..................... 109 5.6 Lauseiden todistaminen.................... 110 5.6.1 Sijoituslauseen todistaminen............. 110 5.6.2 Kootun lauseen todistaminen............. 111 5.6.3 Ehtolauseen todistaminen.............. 112 5.6.4 Toistolauseen todistaminen.............. 114 5.7 Invariantit ohjelmointikielissä................ 116

SISÄLTÖ 5 6 Aliohjelmat 119 6.1 Taustaa ja terminologiaa................... 119 6.2 Aliohjelmien parametriliitäntä................ 120 6.3 Aliohjelmien parametrivälitys................ 122 6.3.1 Parametrivälitysmekanismit............. 122 6.3.2 Arvoparametrit.................... 122 6.3.3 Tulosparametrit.................... 123 6.3.4 Arvo-tulos parametrit................ 124 6.3.5 Viiteparametrit.................... 125 6.3.6 Nimiparametrit.................... 126 6.3.7 Parametrivälitys ohjelmointikielissä......... 127 6.4 Sivuvaikutuksista ja moninimisyydestä............ 128 6.5 Moniulotteiset taulukot parametreina............. 130 6.6 Aliohjelmat parametreina................... 132 6.7 Kuormittaminen........................ 134 6.8 Aliohjelmien toteuttaminen.................. 136 6.9 Rekursiiviset aliohjelmat................... 140 7 Poikkeusten hallinta 143 7.1 Taustaa............................ 143 7.2 PL/I.............................. 144 7.3 Ada.............................. 145 7.4 Eiffel............................. 147 7.5 C++.............................. 148 7.6 Java.............................. 150 7.7 Poikkeusten toteuttaminen.................. 151 8 Rinnakkaisuus 153 8.1 Taustaa............................ 153 8.2 Terminologiaa......................... 154 8.3 Vuorottaisrutiinit....................... 155 8.4 Semaforit eli opastimet.................... 156 8.5 Monitorit........................... 157 8.6 Viestien välittäminen..................... 159 8.6.1 Synkroninen viestien välittäminen.......... 159 8.6.2 Asynkroninen viestien välittäminen......... 163 8.7 Javan säikeet......................... 166 9 Kapselointimekanismit 171 9.1 Taustaa............................ 171 9.2 Abstraktit tietotyypit..................... 172

6 SISÄLTÖ 9.3 Moduulit........................... 173 9.3.1 Modula-2....................... 173 9.3.2 Ada.......................... 177 9.4 Moduulityypit......................... 178 9.5 Luokat............................. 180 10 Olio-ohjelmointi 185 10.1 Taustaa ja terminologiaa................... 185 10.2 Imperatiivisen ja olio-ohjelmoinnin vertailu......... 186 10.3 Oliokielten suunnittelunäkökohtia.............. 187 10.4 Operaatioiden sidonta..................... 190 10.5 Simula............................. 192 10.6 Smalltalk........................... 193 10.7 Eiffel............................. 196 10.8 C++, Java ja C#........................ 198 10.9 Oberon............................ 201 10.10Ada95............................. 205 10.11Luokkien toteuttaminen.................... 206 10.11.1 Dynaamisen sidonnan toteuttaminen......... 206 10.11.2 Yksittäisperiytymisen toteuttaminen......... 208 10.11.3 Moniperiytymisen toteuttaminen........... 209 11 Muotit 213 11.1 Taustaa............................ 213 11.2 Makrot ja muotit....................... 214 11.3 Esimerkki muottien tarpeesta................. 215 11.4 Ada.............................. 216 11.4.1 Muottien objektiparametrit.............. 216 11.4.2 Muottien tyyppiparametrit.............. 217 11.4.3 Muottien aliohjelmaparametrit............ 219 11.5 C++.............................. 220 12 Aspektiohjelmointi 223 12.1 Taustaa............................ 223 12.2 Esimerkkejä aspektien tarpeesta............... 224 12.3 AspectJ............................ 227 12.3.1 Aspektien tunnistaminen............... 227 12.3.2 Koodin liittäminen aspektiin............. 229 12.4 Poikkeusten hallinta aspektina................ 231 12.5 Rinnakkaisuus aspektina................... 233 12.6 Aspektien toteuttaminen................... 234

SISÄLTÖ 7 13 Funktionaalinen ohjelmointi 237 13.1 Ominaisuuksia........................ 237 13.2 Korkeamman asteen funktiot................. 238 13.3 Evaluointijärjestys...................... 239 13.4 Lisp ja Scheme........................ 241 13.4.1 Taustaa........................ 241 13.4.2 Erikoisfunktioita................... 242 13.4.3 Kontrollirakenteet.................. 245 13.4.4 Lambda-lausekkeet.................. 246 13.4.5 Koodia tuottavat funktiot............... 249 13.5 ML.............................. 250 13.6 Haskell............................ 251 14 Logiikkaohjelmointi 253 14.1 Taustaa............................ 253 14.2 Klausuulit ja päättelyprosessi................. 255 14.3 Prologin ominaisuuksia.................... 256 14.3.1 Lauseet........................ 256 14.3.2 Lausekkeiden evaluointi............... 258 14.4 Prologin resoluutio...................... 260 14.5 Prologin tietorakenteet.................... 262 14.6 Prologin kyselyiden tehokkuudesta.............. 265 14.6.1 Alitavoitteiden järjestys............... 265 14.6.2 Katkaisu....................... 266 14.7 Prologin puutteita....................... 268 15 Skriptiohjelmointi 271 15.1 Taustaa............................ 271 15.2 Skriptiohjelmoinnin ja järjestelmäohjelmoinnin vertailu... 272 15.3 Perl.............................. 273 15.3.1 Esittely........................ 273 15.3.2 Tietotyypit...................... 274 15.3.3 Muuttujien näkyvyydet ja elinajat.......... 276 15.3.4 Aliohjelmat...................... 277 15.3.5 Kontrollirakenteet.................. 278 15.3.6 Mallin tunnistaminen................. 279 15.4 Python............................. 279 15.4.1 Esittely........................ 279 15.4.2 Tietoalkiot...................... 280 15.4.3 Kontrollirakenteet.................. 282 15.4.4 Funktiot........................ 284

8 SISÄLTÖ 15.4.5 Moduulit ja luokat.................. 285 15.5 JavaScript........................... 287 15.5.1 Esittely........................ 287 15.5.2 Olioiden luominen.................. 288 15.5.3 Valmiit oliot ja funktiot................ 290 Kirjallisuutta 292 Hakemisto 295

Alkusanat Tämä kirja on kirjoitettu alun perin Tampereen teknillisen yliopiston kurssille Ohjelmointikielten periaatteet, jota olen luennoinut. Kirja on syntynyt käyttämäni luento- ja kurssimateriaalin pohjalta. Tärkeimpänä taustamateriaalina olen käyttänyt seuraavia alan englanninkielisiä oppikirjoja: M. L. Scott. Programming Language Pragmatics. Morgan Kaufmann, 2000. R. W. Sebesta. Concepts of Programming Languages. Addison-Wesley, 2002. R. Sethi. Programming Languages: Concepts & Constructs. Addison-Wesley, 1996. Sebestan kirjasta on olemassa myös uudempi painos vuodelta 2004, mutta mielestäni vuoden 2002 versio on parempi. Ohjelmointikielten periaatteista on tehty opetusmonisteita, joita olen myös käyttänyt kirjan lähteinä: A.-J. Kaijanaho. Ohjelmontikielten periaatteet. Luentomateriaali, Tietotekniikan laitos, Jyväskylän yliopisto, syyskuu 2002, 79 ss. T. Knuutila. Ohjelmointikielten periaatteet. Luentomateriaali. Informaatioteknologian laitos, Turun yliopisto, toukokuu 2001, 148 ss. K. Koskimies. Ohjelmointikielten periaatteet. Luvut 1 3 raportissa C- 1989-3 ja luvut 4 10 raportissa C-1990-1. Tietojenkäsittelyopin laitos, Tampereen yliopisto, joulukuu 1989 helmikuu 1990, yht. 213 ss. Kirja käsittelee ohjelmointikielissä esiintyviä rakenteita, niiden vaihtoehtoja ja toteutustapoja. Kirjassa on esimerkkejä useista eri ohjelmointikielistä, mutta niitä tarkastellaan lähinnä siinä yhteydessä, millaisia piirteitä jokin tietty kieli tarjoaa. Lukijan oletetaan osaavan ohjelmoida edes jollakin ohjelmointikielellä, jolloin hän pystyy tekemään vertailuja tuntemansa kielen ja muiden kielten tarjoamien vaihtoehtojen välillä. Kirja ei käy mitään 9

10 Ohjelmointikielet Periaatteet, käsitteet, valintaperusteet ohjelmointikieltä systemaattisesti läpi, vaan kielistä on esimerkkejä sovitettuna kuhunkin aiheeseen. Kirjassa on joissakin kohdissa käsitelty kielten eri piirteiden suunnittelunäkökohtia. Niitä ei kuitenkaan ole otettu mukaan siinä ajatuksessa, että mitä tulee ottaa huomioon silloin, kun lukija on suunnittelemassa jotakin uutta ohjelmointikieltä. Tämäkään ajatus ei tietenkään ole täysin mahdoton, mutta ensisijainen tarkoitus on se, että tuntiessaan suunnittelunäkökohdat lukija pystyy pohtimaan olemassa olevien kielten ominaisuuksia, näkemään erilaisia vaihtoehtoja ja vertailemaan eri kieliä. Suunnittelunäkökohtien esittelyn tavoitteena on siis auttaa lukijaa huomaamaan, millaisia vaihtoehtoisia piirteitä kielissä on, millainen ratkaisu johonkin tiettyyn kieleen on valittu, millaisia valintaperusteita voi olla ja mitä seurauksia valinnoilla on (esimerkiksi piirteiden toteutukselle). Kirjan kirjoittamisen aikana olen saanut palautetta alan asiantuntijoilta. Käsikirjoitusta ovat lukeneneet ja kommentoineet Kai Koskimies, Tommi Mikkonen ja Mika Katara Tampereen teknillisestä yliopistosta sekä Ari Vesanen Oulun yliopistosta. Lisäksi olen saanut vinkkejä kirjan aihealueeseen liittyen Jorma Tarhiolta Teknillisestä korkeakoulusta. Kiitokset heille kaikille. Tampereella tammikuussa 2005 Maarit Harsu

Luku 1 Johdanto 1.1 Taustaa Käsitteen ohjelmointikieli määritteleminen on vaikeaa. Voidaan ajatella, että ohjelmointikieli on väline tai käyttöliittymä, jolla ihminen pystyy kertomaan tietokoneelle, mitä sen pitäisi tehdä. Tämän määritelmän heikkoutena on kuitenkin liiallinen laajuus. Määritelmän mukaan esimerkiksi HTML (hypertext markup language) luettaisiin ohjelmointikieleksi, mitä ei kuitenkaan yleensä haluta. Määritelmää voidaan rajoittaa kertomalla jotain kielen ilmaisuvoimasta. Tavallisesti ohjelmilla tarkoitetaan sellaisia rakenteisia tekstejä, joita pystytään käsittelemään Turingin koneella. Näin ohjelmointikieli on väline, jolla saadaan tietokone simuloimaan universaalia Turingin konetta. Tässä määritelmässä on kuitenkin se ongelma, että todellisten tietokoneohjelmien kohdalla eteen tulee resurssipula, kun taas Turingin koneen nauha on ääretön. Jos tämän seikan ottaa huomioon määrittelyssä, niin määritelmästä tulee monimutkainen ja hankala käyttää [Kai02]. Alussa esitetyllä määritelmällä on myös se heikkous, että se ei sano mitään siitä, missä muodossa tietokoneelle annettavien komentojen tulee olla. Tietokoneiden alkuaikoina toimintojen valitseminen tapahtui asettamalla kytkimiä sopiviin asentoihin. Kytkinten asettelun määräämää käyttöliittymää ei kuitenkaan yleensä haluta laskea ohjelmointikieleksi. Määritelmää voidaan rajoittaa niin, että ohjelmointikieli on merkintäjärjestelmä, joka kuvaa laskentaa sellaisessa muodossa, että se on sekä ihmisen että koneen luettavissa [Lou03]. Määritelmä sulkee pois myös HTML:n, koska se ei kuvaa laskentaa. Sen sijaan visuaaliset ohjelmointikielet tulevat lasketuiksi mukaan, mutta niitä ei käsitellä kirjassa tämän enempää. Ohjelmointikielten tutkimiseen voidaan löytää useita perusteluita. Oh- 11

12 Ohjelmointikielet Periaatteet, käsitteet, valintaperusteet jelmointikielten yleisten periaatteiden tuntemisella on vaikutusta [Seb02] uusien ohjelmointikielten omaksumiseen ajatusten esittämiskykyyn ohjelmointikielen valintaan kielen toteutuksen ymmärtämiseen omien ohjelmointikielten suunnitteluun. Ohjelmointikielen oppiminen voi perustua aikaisemmin opittuihin kieliin, esimerkiksi C++-ohjelmoijien on helppo oppia Java samoin kuin Pascalosaajien Modula-2. Myös vähemmän toisiaan muistuttavissa kielissä on paljon samanlaisia piirteitä, joita voidaan pitää ohjelmointikielten peruskäsitteinä. Niiden tunteminen helpottaa myös aivan uudenlaisen ohjelmointikielen oppimista. Samoin kuin luonnollisen kielen oppimisessa myös ohjelmointikielen oppimisessa yleiset tiedot kieliopillisista rakenteista edistävät asioiden omaksumista. Ohjelmoijan käyttämän kielen ilmaisuvoima vaikuttaa siihen, millä tarkkuudella tai syvyydellä ohjelmoija pystyy ajattelemaan ja esittämään ajatuksiaan. Erityisesti abstrakteista käsitteistä puhuminen ei ole mahdollista, jos kieli ei sitä tue. Kieli siis rajoittaa sitä, mistä asioista on mahdollista kommunikoida. Vain yhtä ohjelmointikieltä osaava ohjelmoija joutuu rajoittumaan tämän kielen tarjoamiin käsitteisiin. Laajempi tuntemus ohjelmointikielten yleisistä käsitteistä sen sijaan mahdollistaa laajempialaisen pohdinnan ohjelmoinnin ja ohjelmointikielten käsitteistä. Useita ohjelmointikieliä osaava ohjelmoija voi myös simuloida muiden kielten ominaisuuksia ohjelmoidessaan jollakin kielellä, jossa jotakin toivottua ominaisuutta ei ole. Ohjelmoija, joka osaa vain yhtä ohjelmointikieltä, valitsee mielellään tämän kielen ohjelmointitehtäviinsä eikä välttämättä ole halukas oppimaan uusia kieliä. Ohjelmointikielten yleiset periaatteet tunteva ohjelmoija pystyy paljon objektiivisemmin tekemään vertailuja eri kielten välillä ja valitsemaan aina kuhunkin tarkoitukseen sopivimman kielen. Ohjelmointikieleen sisällytettävien ominaisuuksien yhtenä valintakriteerinä on se, miten piirteet voidaan toteuttaa ja miten helppoa toteuttaminen on. Tietämys ohjelmointikielten toteutusnäkökohdista auttaa ymmärtämään, miksi ohjelmointikielet ovat sellaisia kuin ovat. Tämä taas johtaa siihen, että ohjelmoija osaa paremmin käyttää kieltä järkevästi eli niin kuin kieli on suunniteltu käytettäväksi. Toteutusnäkökohtien tunteminen auttaa myös kirjoittamaan tehokkaampia ohjelmia, koska ohjelmoija osaa valita käyttämänsä rakenteet niiden tehokkuuden ja tilanteeseen sopivuuden perusteella. Esi-

Johdanto 13 merkiksi tuntemalla rekursion toteutustavan ohjelmoija ymmärtää, miksi se voi olla tehottomampi kuin vastaava iteratiivinen ratkaisu. Vain hyvin harvat ohjelmoijat suunnittelevat ja toteuttavat oikeita ohjelmointikieliä. Ohjelmointikielten suunnitteluperiaatteista on kuitenkin hyötyä myös pienimuotoisemmissa tehtävissä. Ohjelmissa voidaan joutua lukemaan ja kirjoittamaan erilaisia rakenteisia tekstejä. Tällaisten tehtävien suorittamisessa on hyvä tietää, millainen käsiteltävän tekstin rakenne tulisi olla, jotta sitä olisi helppo ohjelmallisesti lukea, selata ja jäsentää. 1.2 Ohjelmointiparadigmat Sanalla paradigma on useita merkityksiä. Ohjelmointikielten yhteydessä sillä tarkoitetaan kokonaisuutta, joka muodostuu seuraavista kolmesta osasta: laskennallisesta mallista, käsitteistöstä ja välineistöstä [BG94]. Perinteisessä imperatiivisessa paradigmassa laskennallinen malli perustuu RAMkoneeseen (random access machine) eli hajasaantikoneeseen. Jos halutaan toteuttaa esimerkiksi lajitteluohjelma, käsitteistöön kuuluvat kielen rakenteet kuten tietueet, osoittimet, toistorakenteet ja tiedostot. Välineistö taas tarkoittaa sitä, miten ohjelmoija kielen käsitteitä käyttää. Lajitteluohjelman tapauksessa välineistöä voivat olla linkitetty lista sekä syöttö- ja tulostustoiminnot. Yleisesti ottaen paradigma antaa mahdollisuudet tai puitteet eri asioiden toteuttamiseen. Eri paradigmoissa on erilaiset mahdollisuudet eri asioiden toteuttamiseen. Jotkin tehtävät onnistuvat helpommin jossakin ohjelmointiparadigmassa, jotkin toiset asiat taas jossakin toisessa paradigmassa. Ohjelmointikielien jaottelu eri paradigmoihin ei ole välttämättä kovin helppoa. Yksi usein käytetty jaottelu on [Weg89]: Imperatiivinen paradigma (käskykielet) lohkorakenteinen (proseduraalinen) oliokeskeinen hajautettu (rinnakkainen) Deklaratiivinen paradigma (esittelykielet) funktionaalinen looginen tietokantakielet.

14 Ohjelmointikielet Periaatteet, käsitteet, valintaperusteet Jaottelussa ei kuitenkaan pystytä erottelemaan esimerkiksi rinnakkaista funktionaalista paradigmaa tavanomaisesta (peräkkäisestä) funktionaalisesta paradigmasta, mikä onnistuu jaottelussa [BG94]: Paradigma imperatiivinen (proseduraalinen) oliokeskeinen funktionaalinen looginen Ohjelmointimalli peräkkäinen rinnakkainen. Jaottelussa valitaan paradigma, ja valittuun paradigmaan liitetään ohjelmointimalli, joka voi olla peräkkäinen tai rinnakkainen. Ohjelmointimallin ansiosta erottelu rinnakkaisten funktionaalisten kielten ja peräkkäisten funktionaalisten kielten välillä onnistuu. Tälläkin jaottelulla on kuitenkin puutteensa. Se ei pysty sovittamaan esimerkiksi funktionaalista oliokieltä mihinkään ryhmään, mikä onnistuisi, jos myös oliokeskeisyys otettaisiin mukaan omana ohjelmointimallinaan samalla tavalla kuin tieto rinnakkaisuudesta tai peräkkäisyydestä. Kahdessa edellä esitetyssä jaottelussa suhtaudutaan eri tavalla siihen, kuuluvatko oliokielet mukaan imperatiiviseen paradigmaan vai eivät. Jos halutaan erityisesti korostaa imperatiivisuutta (ilman oliokeskeisyyttä), voidaan puhua proseduraalista kielistä. Jatkossa tässä kirjassa otetaan se kanta, että imperatiivinen paradigma ei sisällä oliokieliä. Ohjelmointikielten jaotteluita on muitakin. Osterhout erottelee yhteen ryhmään järjestelmäohjelmointikielet, joita ovat esimerkiksi Pascal, C, C++ ja Java, ja toiseen ryhmään skriptikielet, kuten Perl, Python ja JavaScript [Ous98]. Järjestelmäohjelmointikielet on tarkoitettu komponenttien toteuttamiseen symbolisia konekieliä korkeammalla tasolla, kun taas skriptikielillä voidaan yhdistellä olemassa olevia komponentteja toisiinsa. Paradigmojen jaotteluun ei siis ole olemassa yhtä yleisesti hyväksyttyä tapaa. Vaikka jaottelu olisi millainen tahansa, ongelmia syntyy ainakin siinä, että on olemassa myös moniparadigmakieliä, jotka sisältävät piirteitä useasta eri paradigmasta. Lisäksi ohjelmointikieliin tulee uusia ominaisuuksia niin, että uudet kielet eivät aina helposti sovi olemassa oleviin jaotteluihin. Esimerkkinä uusista ominaisuuksista ovat joidenkin kielten aspektiominaisuudet, vaikkakaan aspektiohjelmointia ei (ainakaan vielä) voi pitää

Johdanto 15 omana paradigmanaan, koska aspektikielillä on mahdollista ohjelmoida välittämättä aspektiominaisuuksista. Tarkastellaan seuraavaksi eri paradigmojen ominaispiirteitä. Imperatiiviselle paradigmalle olennaista on tuhoava sijoituslause, jossa uuden arvon sijoittaminen tuhoaa vanhan arvon. Sijoituslause on myös sikäli oleellisessa asemassa, että laskenta perustuu siihen, millaisia arvoja eri muuttujilla eri tilanteissa on. Kontrollin siirtäminen tapahtuu eksplisiittisesti, ja kontrollin kulkua on suhteellisen helppo seurata ohjelmatekstistä. Esimerkkejä imperatiivista paradigmaa noudattavista kielistä ovat Fortran, Pascal, C ja Ada83. Oliokeskeisessä paradigmassa oliot ovat tärkeässä asemassa: niitä luodaan, ja ne välittävät ja vastaanottavat viestejä. Samanlaiset (saman tyyppiset) oliot kuuluvat samaan luokkaan. Luokkia voidaan myös periyttää toisistaan. Esimerkkejä oliokielistä ovat Simula67, Eiffel ja Smalltalk. C++ on hybridi kieli siinä mielessä, että se sisältää sekä imperatiivisen että olioohjelmoinnin piirteitä. Funktionaalisissa kielissä laskenta tapahtuu funktioiden avulla, joilla ei yleensä ole sivuvaikutuksia. Puhtaissa funktionaalisissa kielissä ei ole sijoituslausetta, vaan kaikki arvot saadaan funktioiden tuloksina. Esimerkkejä funktionaalisista kielistä ovat Lisp, Scheme (Lispin murre), ML (Meta Language), Haskell ja Miranda. Näistä kahta jälkimmäistä voidaan pitää puhtaana funktionaalisena kielenä. Loogisessa paradigmassa keskeisessä asemassa ovat logiikan kaavat. Ohjelman suoritus tarkoittaa loogisen väittämän todistamista ohjelman loogiseksi seuraukseksi. Looginen paradigma on suhteellisen uusi, se on syntynyt vasta 1970-luvulla. Yleisin käytettävä logiikkaohjelmoinnin kieli on Prolog, jolla on useita murteita. Rinnakkaisessa paradigmassa oleellisessa asemassa ovat prosessit, joita voi olla useita yhtäaikaa suoritettavana. Yhtäaikaisesta suorituksesta johtuen tärkeää on myös prosessien tahdistaminen eli synkronointi, jolla varmistetaan, että prosessit ovat sopivissa suorituksen vaiheissa keskinäisen tiedon välityksen onnistumiseksi. Rinnakkaisia ohjelmointikieliä ovat Occam ja SR (Syncronizing Resources). Myös Java ja Ada sisältävät välineitä rinnakkaiseen ohjelmointiin. 1.3 Ohjelmointikielten historiaa Kuvassa 1.1 on esitetty joitakin ohjelmointikieliä ja niiden pohjautumista toisiinsa. Mustalla pallolla merkityistä kielistä on nimi näkyvissä. Useimmat näistä kielistä esiintyvät kirjan myöhemmissä esimerkeissä. Valkoisella

16 Ohjelmointikielet Periaatteet, käsitteet, valintaperusteet pallolla merkittyjen kielten nimi ei ole näkyvissä. Nimen puuttuminen voi johtua siitä, että kieli ei ole kovin merkittävä tämän kirjan kannalta tai siitä, että nimen poisjättämisellä on saatu kuvaa vähän selvemmäksi. Kuvasta 1.1 voidaan löytää joitakin kehityslinjoja. Fortranin eri versiot muodostavat oman linjansa. Fortran II on merkitty valkoisella pallolla. Fortran III on ollut olemassa, mutta se ei koskaan levinnyt laajaan käyttöön, eikä sitä myöskään näy kuvassa. Fortran90:n nimi on jätetty pois kuvan selventämiseksi. C-perheen kielen kielet erottuvat omana linjanaan. B-kielen edeltäjän BCPL:n nimi puuttuu. Algol-perheen kieliin kuuluvat esimerkiksi Pascal ja Modula-2. Funktionaalisilla kielillä on oma linjansa, joka kuitenkin yhdistyy imperatiivisiin kieliin Pascalin kautta. Logiikkakieli Prolog ei ole yhteydessä muihin kieliin. Ohjelmointikielten historia [Seb02, Kos89] alkaa 1950-luvun alkupuolelta, jolloin symbolisten konekielten siirrettävyysongelmiin alettiin etsiä ratkaisua automaattisesta ohjelmoinnista. Siinä ohjelmoija esittää ongelman kuvauksen jollakin formaalilla kielellä, minkä jälkeen jokin erityinen ohjelma tuottaa siitä konekielisen ohjelman, joka ratkaisee ongelman. Nykyisessä terminologiassa formaalit kielet tarkoittavat korkean tason kieliä ja konekielisen ratkaisun tuottajia kutsutaan kääntäjiksi. Aluksi ihmisten oli vaikea uskoa, että automaattinen ohjelmointi voisi olla mahdollista. Fortrania voidaan pitää nykyisten lausekielten kantaisänä, joka myös edisti luottamusta automaattisen ohjelmoinnin mahdollisuuksiin. Tosin ensimmäisen Fortran-kääntäjän toteuttaminen oli vaikea tehtävä: siihen meni kolme vuotta ja 18 ihmistyövuotta. Pääkehittäjänä toimi John Backus, ja kehitys tapahtui IBM:n tuella. Fortranissa oli joitakin nykyisten ohjelmointikielten piirteitä: taulukkomuuttujat, toistolause (for-silmukkaa vastaava) ja valintalause (if-lausetta vastaava). Fortran levisi laajaan käyttöön, ja sen uudemmat versiot ovat edelleen käytössä. Fortranin tieteellisen laskennan vastapainoksi John McCarthy havaitsi tarpeen symboliselle tiedonkäsittelylle. Aluksi hän yritti laajentaa Fortrania tähän suuntaan, mutta totesi kuitenkin paremmaksi vaihtoehdoksi kehittää uuden kielen: Lispin. Kehitystyö tapahtui aluksi paperilla, myös Lispohjelmat käännettiin aluksi paperilla. Sitten huomattiin, että alun perin teoreettiseksi leluksi tarkoitettu Lisp-funktio eval voi toimia Lisp-tulkkina. Lispin kehitys on myöhemmin johtanut muiden funktionaalisten kielten kehittymiseen. Myös Cobol kehitettiin 1950-luvulla kaupallista laskentaa ja hallinnollisia sovelluksia varten. Kieli on vieläkin käytössä. Seuraavaksi kehitys johti Algol60:n syntymiseen, koska haluttiin laitevalmistajasta riippumaton algoritmien toteuttamiseen tarkoitettu kieli. Kehitystyö tapahtui saksalais-amerikkalaisena yhteistyönä. Algol60 oli ensim-

Johdanto 17 1957 1960 Cobol Fortran I Fortran IV CPL PL/I Basic Algol60 Simula I Lisp 1970 1980 B C Fortran 77 C++ Algol68 Pascal Modula-2 Smalltalk Ada83 Simula67 ML Prolog Scheme Common Lisp 1990 Oberon Modula-3 Visual Basic Eiffel Haskell Java Fortran 95 Ada95 2000 C# Kuva 1.1: Ohjelmointikielten sukupuu

18 Ohjelmointikielet Periaatteet, käsitteet, valintaperusteet mäinen lohkorakenteinen kieli, lisäksi siinä oli mukana nykyisen kaltaiset kontrollirakenteet ja rekursio. Algol60:n suunnittelussa pidettiin tärkeänä sitä, että ohjelmat pystytään mekaanisesti kääntämään konekielelle. Algol60:n suunnitteluprosessin tuloksena saatiinkin uusi notaatio ohjelmointikielten syntaksin kuvaamiseen: BNF (Backus-Naur form). Algol60 ei kuitenkaan akateemisen maineensa vuoksi levinnyt niin laajaan käyttöön kuin Fortran, mutta sillä on ollut kuitenkin tärkeä merkitys myöhemmin kehitettyjen kielten suunnittelussa. Algol60:n pohjalta kehitettiin useita merkittäviä kieliä. Yksi näistä oli Norjassa kehitetty Simula. Kielen keskeisenä ajatuksena oli simulointi, josta johdettiin luokan käsite. Simula oli siis ensimmäinen oliokieli. Simula I:ssa oli mukana luokat ja oliot, Simula67:ssa mukaan otettiin myös periytyminen. Myös Basic syntyi 1960-luvulla. Sen kehitysperiaatteena oli luoda helppo kieli, jonka käyttö olisi helppo opettaa kenelle tahansa. Basicin lähtökohtana oli Algol60, mutta monia tärkeitä piirteitä jätettiin pois, esimerkiksi lohkorakenne. Sitä pidettiin liian vaikeana. Edelleen 1960-luvulla kehitettiin PL/I (tai PL/1, programming language one). Sen oli tarkoitus olla universaali ohjelmointikieli, joka sopisi erilaisiin tehtäviin. Tuolloin ohjelmointitehtävät (ja ohjelmoijat) voitiin jakaa kahteen ryhmään. Toinen ryhmä harrasti tieteellistä laskentaa, jossa käsiteltiin liukulukuja, taulukoita ja aliohjelmia. Toisen ryhmän tehtävänä taas oli hallinnollinen tietojenkäsittely, jossa tarvittiin kiintolukulaskentaa, tehokasta siirräntää (I/O) ja merkkijonojen käsittelyä. PL/I:n tarkoitus oli sopia molempiin tarkoituksiin, ja siihen yhdistettiin piirteitä Fortranista, Algol60:sta ja Cobolista. Kieli oli 1970-luvulla suosittu, vaikka sitä voidaan pitää laajana ja vaikeana kielenä. Siinä oli kuitenkin joitakin tärkeitä uusia piirteitä kuten poikkeusten hallinta ja rinnakkaisuus. Algol60:n pohjalta alettiin kehittää uutta kieltä: Algol68:a. Kielen käsitteistön oli tarkoitus olla systemaattisempi kuin Algol60:n. Kieli jäi kuitenkin enimmäkseen käyttämättä laajuutensa ja vaikeaselkoisen määrittelynsä vuoksi. Algol68:n kehityksessä oli mukana Niklaus Wirth, joka paheksui Algol68:n monimutkaisuutta. Hän jäi kuitenkin mielipiteensä kanssa vähemmistöön. Tästä syystä Wirth alkoi kehittää kieltä, joka olisi selkeä, yksinkertainen ja helppo toteuttaa. Näin syntyi Pascal. Siinä oli selkeä tietotyypin käsite, jonka avulla käyttäjä pystyi määrittelemään omia tietotyyppejään. Pascalissa oli paljon hyviä oivalluksia, mutta myös joitakin puutteita. Pascal on ollut lähtökohtana monille myöhemmille kielille, ja yksi tärkeä merkitys on se, että Pascalin puutteet osattiin ottaa huomioon myöhemmissä kielissä. Wirth on kehittänyt Pascalin pohjalta Modula-2:n ja Oberonin.

Johdanto 19 Ohjelmistotuotanto kasvoi voimakkaasti 1970-luvulla, ja laitekustannukset pienenivät. Näin ohjelmointikustannusten merkitys tuli hyvin oleellisiksi. Ohjelmointi oli kuitenkin hyvin virhealtista, ja tarvittavia resursseja oli vaikea ennakoida. Käytössä oli useita eri kieliä ja niiden murteita, ja ohjelmat olivat vaikeasti siirrettäviä. Lopulta ajauduttiin niin kutsuttuun ohjelmistokriisiin. Ratkaisuna kriisiin oli rakenteellinen ohjelmointi, jota monet 1970-luvulla kehitetyistä kielistä tukivat. Usein kielet tukivat myös laajempaa ohjelmistotuotantoa, esimerkiksi Modula-2 moduulikäsitteen muodossa. Myös tietoabstraktio löytyy monista tämän aikakauden kielistä. Dennis Ritchie kehitti 1970-luvulla C-kielen, joka sai vaikutteita Algol68:sta, vaikka ei sitä syntaksiltaan muistutakaan. Kielen varsinaisia edeltäjiä ovat CPL, BCPL ja B. Sekä BCPL että B ovat tyypittömiä kieliä, mikä oli ajalle epätyypillistä. C oli alunperin tarkoitettu raakaan järjestelmäohjelmointiin, ja sillä kirjoitettiin Unix-käyttöjärjestelmä kokonaan uudestaan. C-kielen tyyppitarkistukset eivät ole täydellisiä, mistä syystä ohjelmista saadaan toisaalta joustavia ja tehokkaita mutta toisaalta virhealttiita. C-kielessä ei käsitteellisesti ole juurikaan mitään uutta, mutta siitä tuli hyvin suosittu ja siksi merkittävä. Funktionaalisten kielten kehittelyä jatkettiin 1970-luvulla, jolloin syntyi useita Lisp-johdannaisia, esimerkiksi Scheme. Alkunsa sai myös ML, joka on tyypitetty funktionaalinen kieli. Siinä on paljon vaikutteita Lispistä, mutta se on syntaksiltaan Algol-tyyppinen. Myös oliokieliä kehitettiin, jolloin syntyi Smalltalk. Sitä voidaan pitää puhtaimpana oliokielenä. Itse kieli on hyvin pieni, mutta sen peruskirjasto on hyvin suuri. Lisäksi kehitettiin myös Prolog, joka logiikkakielenä on täysin erilainen kuin muut ohjelmointikielet. Seuraavalla vuosikymmenellä syntyi Ada (Ada83), joka kehitettiin Yhdysvaltain puolustusministeriön toimeksiannosta. Kieli on saanut nimensä Augusta Ada Byronin mukaan, jonka on sanottu olleen maailman ensimmäinen ohjelmoija. Ada syntyi tarkan vaatimusraportin pohjalta. Kehitystyössä tarkasteltiin olemassa olevia kieliä, ja etsittiin niiden joukosta sopivia lähtökohtia uudelle kielelle. Adan perusajatuksena oli saada ohjelmista luotettavia, siksi kielessä on vahva tyypitys, joten virheet huomataan mahdollisimman aikaisessa vaiheessa. Muita kielen piirteitä ovat tietoabstraktio, rinnakkaisuus, geneerisyys (tyyppien parametrointi) ja poikkeusten hallinta. Kieltä pidettiin aikanaan laajana mutta silti suhteellisen helppona oppia. Ada83:n pohjalta on myöhemmin kehitetty Ada95, johon on lisätty olioparadigman piirteitä kuten periytyminen ja monimuotoisuus (polymorfisuus). Myös rinnakkaisuuteen liittyviä mekanismeja on parannettu. Uusia oliokieliä kehitettiin 1980-luvulla. Bjarne Stroustrup laajensi C- kieltä olioilla, jolloin syntyi C++. Bertrand Meyer kehitti Eiffel-kielen, jota

20 Ohjelmointikielet Periaatteet, käsitteet, valintaperusteet voidaan pitää puhtaana oliokielenä. Syntaksiltaan Eiffel muistuttaa Adaa. Ohjelmissa on mahdollista käyttää esi- ja jälkiehtoja sekä invariantteja, joten kieli tukee ohjelmien formaalia todistamista. Olio-ohjelmointi yleistyi 1990-luvulla, jolloin C++:n pohjalta kehitettiin Java. Tarkoituksena oli säilyttää C-kielestä lähtöisin oleva joustavuus mutta samalla parantaa turvallisuutta. Javassa ei ole osoittimia, vaan ne on korvattu viitteillä. Kielessä on automaattinen roskien keruu. Javassa moniperiytyminen ei ole mahdollista muuten kuin rajapintojen avulla. Javan ja C++:n pohjalta on myöhemmin kehitetty C#. Kun Javassa joitakin C++:n piirteitä jätettiin pois, niin C#:ssa monet näistä piirteistä on tuotu takaisin, tosin hieman eri muodossa. Myös Internetin käyttö alkoi ja yleistyi 1990-luvulla, millä oli vaikutusta ohjelmointikielten kehitykseen, erityisesti skriptikielten yleistymiseen. Kielet sopivat hyvin HTML-dokumenttien luomiseen ja käsittelemiseen, koska niillä kirjoitettua koodia voidaan helposti sisällyttää HTML-dokumentteihin. 1.4 Ohjelmointikielten sukupolvet Ohjelmointikielten kehittyminen jaetaan tavanomaisesti viiteen sukupolveen. Eri sukupolvet kuvaavat sitä, miten lähellä kieli on suorittavaa laitetta ja toisaalta miten kaukana se on ohjelmoinnin kohteesta eli sovellusalueesta. Ensimmäisen sukupolven kielet olivat konekieliä. Tällöin ohjelmointi oli sananmukaisesti oli koodaamista: ratkaisualgoritmi piti muuttaa binaarikoodiksi. Muistiosoitteet olivat viittauksia absoluuttisiin muistiosoitteisiin. Jos ohjelman keskelle piti lisätä uusi käsky, ohjelman loppuosan sijainti muistissa muuttui, joten myös loppuosaan kohdistuvat viittaukset piti päivittää. Toisen sukupolven kielillä eli symbolisella konekielillä ohjelmointi oli huomattavasti helpompaa. Käskyillä oli selkeät nimet ja muistiosoitteet olivat symbolisia. Ohjelmia voitiin kääntää erillisiksi moduuleiksi, jotka voitiin myöhemmin linkittää suoritettavaksi ohjelmaksi. Ohjelmia oli kuitenkin vaikea lukea (varsinkin muiden kuin alkuperäisen kirjoittajan), ja ne olivat hyvin koneriippuvia. Kolmannen sukupolven kielillä tarkoitetaan korkean tason lausekieliä, joita ovat useimmat nykyisin käytössä olevat ohjelmointikielet. Tyypillisiä piirteitä ovat abstraktit kontrollirakenteet ja tietoalkioiden tyypitys. Aliohjelmien avulla voidaan rakentaa uusia abstrakteja käskyjä. Ohjelmia on suhteellisen helppo lukea, ja ne ovat helposti siirrettäviä. Neljättä sukupolvea tarkoittava lyhenne 4GL esiintyy usein sovelluskehittimien yhteydessä. Sovelluskehitintä voidaan ajatella ohjelmana, jol-

Johdanto 21 la pystyy helposti generoimaan jonkin tietyn sovellusalueen ohjelmia. Tällaiset ohjelmointikielet ovat kuitenkin epäyhtenäisiä, esimerkiksi tietyn valmistajan kaupallisia tuotteita. Sovelluskehittimissä kieli on usein liitetty graafiseen käyttöliittymään. Viidennen sukupolven kielillä eli asiantuntijajärjestelmillä pyritään mallintamaan ihmisen ajattelua ja päätöksentekoa. Edellä esitettyä sukupolvijaottelua voidaan pitää keinotekoisena siinä mielessä, että useimmat nykyisistä ohjelmointikielistä kuuluvat kolmanteen sukupolveen. Neljäs ja viides sukupolvi nähdään usein vain mainoslauseina. Lisäksi funktionaalinen ohjelmointi ei selvästi kuulu mihinkään sukupolveen. Järkevämpää onkin esittää pidemmälle menevä jaottelu kolmannen sukupolven sisällä [Knu01]: 1. koneriippumattomat kielet 2. tyyppi- ja kontrollirakenteita sisältävät kielet 3. kontrolliabstraktiota (aliohjelmia) tukevat kielet 4. tietoabstraktiota (moduuleja, olioita) tukevat kielet 5. geneerisyyttä ja monimuotoisuutta tukevat kielet. Luettelossa esiintyvät kielten ominaisuudet tulevat tarkemmin esiin kirjan myöhemmissä luvuissa. 1.5 Esimerkki ohjelmointikielten kehittymisestä Tässä kohdassa tarkastellaan esimerkin avulla, millaisten vaiheiden kautta ohjelmointikielten kehittyminen voi tapahtua. Tavoitteena on usein kielten tason nostaminen eli tehtävien abstrahointi laitetasolta lähemmäksi ihmisen ajattelutavan käsitteitä. Esimerkiksi kontrollirakenteet ovat kehittyneet niin, että symbolisella konekielellä kirjoitetuissa ohjelmissa on havaittu usein esiintyviä samojen komentojen jonoja: LDX... CMPX... JG...

22 Ohjelmointikielet Periaatteet, käsitteet, valintaperusteet Tämä komentojono voidaan abstrahoida vertailun perusteella tapahtuvaksi hypyksi. Korkeamman tason kieliin (esimerkiksi Fortraniin) on sitten lisätty ehdollisen hypyn toteuttava kontrollirakenne (matemaattinen valintalause): IF ( expr ) GOTO address Myöhemmässä vaiheessa valintalauseen käyttöjä tarkastelemalla voidaan havaita usein esiintyviä rakenteita: 10: IF ( expr ) GOTO 20... GOTO 10 20:... Koodissa tapahtuu toisto, joka jatkuu niin kauan kun ehto on voimassa. Myöhemmissä kielissä (esimerkiksi Algol60) tämä on abstrahoitu toistolauseen toteuttavaksi kontrollirakenteeksi: while expr do... Edellisen kaltaiselle kehitykselle voidaan löytää mahdollisuuksia myös nykyisissä ohjelmointikielissä. Esimerkiksi suunnittelumalleja (design pattern) voidaan pitää usein esiintyvinä luokkien kokoelmina, jossa luokilla on samanlaiset suhteet toisiinsa. Suunnittelumallit on siis koottu matalamman tason käsitteistä eli luokista ja niiden välisistä suhteista. Vastaavanlainen kehitys kuin edellä esitetty voisikin johtaa mallikieliin (pattern language), jotka kertovat, miten ja missä tilanteissa suunnittelumalleja voidaan käyttää. Jonkin verran tällaisia on jo olemassa. Ohjelmointikielten kehitys voi siis tapahtua olemassa olevien piirteiden pohjalta. Kuitenkin myös uusien laskentamallien kehitys vaikuttaa ohjelmointikielten kehittymiseen, eikä kaikkia uusia piirteitä voida mallintaa vanhojen pohjalta. Esimerkiksi rinnakkaisuuden kehittyminen ohjelmointikieliin on vaatinut tuekseen laitteistotasolla mahdollistettavan rinnakkaisuuden. 1.6 Ohjelmointikielen suunnittelu Ohjelmointikielille voidaan asettaa erilaisia vaatimuksia. Yleisenä vaatimuksena voidaan mainita se, että kieltä voidaan helposti käyttää siihen tehtä-

Johdanto 23 Taulukko 1.1: Kielen ominaisuuksien arviointi Arviointiperusteet Kielen ominaisuuksia Luettavuus Kirjoitettavuus Luotettavuus Yksinkertaisuus Ortogonaalisuus Kontrollirakenteet Tietotyypit Syntaktiset ratkaisut Ilmaisuvoima Abstrahoinnin tukeminen Virheiden havaitseminen Oikeellisuus vään, mihin se on tarkoitettu. Eri kielille voidaan asettaa muitakin vaatimuksia, jotka riippuvat kielen tarkoituksesta. Erilaisia vaatimuksia löytyy lähteistä [Knu01, Seb02]. Kielen suunnitteluperusteita voidaan tarkastella taulukon 1.1 mukaisesti. Taulukko perustuu Sebestan kirjassa esitettyyn taulukkoon [Seb02]. Luettavuuteen vaikuttaa ensinnäkin yksinkertaisuus. Jos kieli on hyvin suuri, ohjelmoijilla on taipumus käyttää vain jotakin osajoukkoa kielen ominaisuuksista. Toisten ohjelmoijien kirjoittamia ohjelmia on silloin vaikea lukea, koska he ovat voineet ottaa käyttöönsä jonkin toisen osajoukon kielestä. Yksinkertaisissa kielissä on yleensä vain yksi tapa ilmaista jokin tietty asia. Jos tapoja on useita, kielen lukeminen on vaikeampaa. Liian pitkälle vietynä yksinkertaisuus voi kuitenkin vaikeuttaa lukemista. Esimerkiksi symboliset konekielet ovat yksinkertaisia, mutta niiden lukeminen ei ole helppoa. Ortogonaalisuus tarkoittaa kielen rakenteiden riippumattomuutta toisistaan. Ortogonaalisessa kielessä on suhteellisen pieni joukko rakenteita, joita voi yhdistellä suhteellisen harvoilla tavoilla. Jokainen yhdistelytapa on laillinen, ja sillä on järkevä tulkinta. Kielen säännöissä ei siis juurikaan ole poikkeuksia. Tämä edistää ohjelmien luettavuutta. Olemassa olevista kielistä on löydettävissä joitakin epäortogonaalisia piirteitä. Esimerkiksi Pascalissa funktio voi palauttaa muun tyyppisiä arvoja mutta ei taulukoita. C:ssä taas on inkrementointiin neljä eri tapaa, jotka ainakin joissakin tilanteissa voivat tarkoittaa täysin samaa asiaa. Lisäksi C:ssä parametrit ovat yleensä arvoparametreja, mutta taulukot käsitellään viiteparametreina. Monien mielestä funktionaalisissa kielissä yhdistyy parhaiten ortogonaalisuus ja yksinkertaisuus. Liian pitkälle vietynä ortogonaalisuus voi kuitenkin tehdä kielestä monimutkaisen. Tästä esimerkkinä on Algol68, jossa jokaisella kielen rakenteella on tyyppi ja useimmat kielen rakenteet tuottavat jonkin arvon.

24 Ohjelmointikielet Periaatteet, käsitteet, valintaperusteet Tällöin esimerkiksi ehtolause voi olla sijoituslauseen vasemmalla puolella, kunhan ehtolauseen tuloksena on osoite. Kontrollirakenteet (ja rakenteellinen ohjelmointi) parantavat ohjelmien luettavuutta. Esimerkkinä tarkastellaan sisäkkäisiä silmukoita C-kielen syntaksilla kirjoitettuna: while ( incr < 20 ) { while ( sum <= 100 ) { sum += incr; } incr++; } Jos kielessä ei ole while-lausetta, vastaava koodi joudutaan kirjoittamaan hankalammin, mikä vaikeuttaa ymmärtämistä: Loop1: if ( incr >= 20 ) goto Out; Loop2: if ( sum > 100 ) goto Next; sum += incr; goto Loop2; Next: incr++; goto Loop1; Out: Kieleen valitut tietotyypit vaikuttavat luettavuuteen, esimerkiksi looginen tyyppi (boolean) selventää muuttujan merkitystä verrattuna siihen, että loogisen arvojen sijasta käytettäisiin numeerisia arvoja. Myös tietuetyypit selventävät ohjelmaa, koska niiden avulla yhteenkuuluvat arvot voidaan koota yhteen. Kielen syntaksi vaikuttaa luettavuuteen. Tunnusten pituuden rajoittaminen vähentää luettavuutta, koska tunnukset eivät silloin yleensä ole kuvaavia. Syntaksissa määritellyt varatut sanat taas auttavat tunnistamaan kielen kontrollirakenteet muusta ohjelmakoodista, mikä helpottaa ohjelmien lukemista. Kirjoitettavuudella tarkoitetaan sitä, miten helposti kieltä pystytään käyttämään ohjelmien tuottamiseen. Useimmat luettavuuteen vaikuttavat kielen piirteet vaikuttavat myös kirjoitettavuuteen, koska ohjelmia kirjoitettaessa joudutaan jo kirjoitettuja osia samalla lukemaan. Vaikutukset ovat lisäksi yleensä samansuuntaisia: se, mikä helpottaa lukemista, helpottaa myös kirjoittamista. Tosin yksinkertaisuuden kohdalla vaikutuksena on lisäksi se, että yksinkertaisessa kielessä kirjoitusvirheiden mahdollisuus on pienempi.

Johdanto 25 Jos taas kieli on laaja, niin tarvitessaan harvoin käyttämiään rakenteita ohjelmoija tekee helposti virheitä. Samoin jos kieli on liian ortogonaalinen, ohjelmoija ei helposti huomaa virheitä, jotka tapahtuvat rakenteiden yhdistelyssä. Kielen ilmaisuvoima ja abstrahoinnin tukeminen liittyvät läheisesti toisiinsa. Ilmaisuvoimaan vaikuttaa esimerkiksi kieleen valittujen toimintojen määrä. Jos kielessä ei ole tarpeeksi toimintoja, ne joudutaan tuottamaan ohjelmallisesti, mihin voi kulua paljon aikaa. Ilmaisuvoimaan vaikuttaa myös se, miten lähellä kielen rakenteet ovat ihmisen ajattelurakenteita. Esimerkiksi symbolisissa konekielissä ratkaisut joudutaan esittämään käyttämällä koneen terminologiaa, kun taas korkeamman tason kielissä ratkaisut voidaan esittää sovellusalueen terminologialla. Korkeamman tason kielessä on enemmän abstrahointimahdollisuuksia. Abstrahointi tukee ohjelmien kirjoittamista niin, että kaikkia yksityiskohtia ei tarvitse ottaa huomioon ohjelmia kirjoitettaessa. Esimerkiksi toimintojen abstrahointi aliohjelmien sisään vähentää kirjoitustyötä ja mahdollisten virheiden määrää. Ilmaisuvoiman ja abstrahoinnin yhteydessä mainittu kielen taso kertoo, missä kohdassa kieli sijaitsee kone-sovellusalue akselilla. Mitä korkeammalla tasolla kieli on, sitä riippumattomampi se on tietokoneesta ja sitä enemmän se tukee sovellusaluetta (vrt. sovelluskehittimet). Jos kielen taso määritellään suhteessa sovellusalueeseen, ei ole järkevää vertailla eri sovellusalueille tarkoitettuja kieliä keskenään. Kieli voi toisaalta olla yleiskäyttöinen eli sellainen, että se sopii useille eri sovellusalueille. Tällöin kieltä voidaan käyttää hyvin laajoilla ja epäyhtenäisillä sovellusalueilla, mistä yleensä seuraa, että kieli ei voi olla kovin korkealla tasolla. Ohjelmaa voidaan pitää luotettavana, jos se toimii määrittelynsä mukaisesti. Jos ohjelmia on helppo lukea ja kirjoittaa, ne myös yleensä ovat luotettavampia. Ohjelmointikielen suunnittelussa joudutaan ottamaan kantaa siihen, missä vaiheessa virheet (esimerkiksi tyyppivirheet) havaitaan. Virheiden havaitseminen mahdollisimman aikaisessa vaiheessa parantaa ohjelmien luotettavuutta, toisaalta taas tarkistusten pois jättäminen tekee kielestä joustavan. Luotettavuuteen liittyy myös poikkeusten hallintamekanismin suunnittelu. Oikeellisuudella viitataan ohjelmien todistamiseen ja oikeellisuudesta varmistumiseen. Vaikka ohjelmia ei formaalisti todistettaisikaan oikeiksi, jo kielen rakenteiden selkeys auttaa varmistumaan siitä, että ohjelma toimii oikein. Esimerkiksi rakenteellinen ohjelmointi ja modulaarisuus tukevat vakuuttumista oikeellisuudesta, kun taas goto-lauseiden esiintyminen ohjelmissa vaikeuttaa sitä. Joissakin kielissä on mahdollisuus käyttää invariantteja eli pysyväisväittämiä, joiden automaattinen tarkistaminen lisää ohjelmien luotettavuutta.

26 Ohjelmointikielet Periaatteet, käsitteet, valintaperusteet Taulukon 1.1 kohtien lisäksi ohjelmointikielen ominaisuuksia voidaan tarkastella siltä kannalta, miten kieli tukee koko ohjelmistoprosessia eli millaisia taloudellisia vaikutuksia sillä on. Kieli tukee suunnittelua, jos se sisältää tarvittavia abstrahointivälineitä, esimerkiksi tietoabstraktion ja kontrolliabstraktion (aliohjelmat). Ylläpitoa taas helpottaa se, että ohjelma voidaan jakaa osiin, joilla on mahdollisimman vähän liittymiä toisiinsa. Tällöin muutosten vaikutukset järjestelmän muihin osiin voidaan minimoida. Kieli vaikuttaa ohjelmistoprosessiin, ja hyväksi todetut kehitysmenetelmät vaikuttavat kieliin. Esimerkiksi top-down suunnittelun tukemiseksi ohjelmointikieliin on lisätty mahdollisuuksia rakenteelliseen ohjelmointiin. Toisena esimerkkinä kaksisuuntaisesta vaikutuksesta on se, että ohjelmistojen jatkuvasti kasvaessa on huomattu tarve ohjelmistojen jakamiselle osiin. Tämän seurauksena ohjelmointikieliin on lisätty modulaarisuutta tukevia välineitä. Myös tehokkuusnäkökohdat tulee ottaa huomioon kielen suunnittelussa. Toteutuksen tehokkuudella voidaan viitata toisaalta siihen, miten tehokkaasti kääntäjä voidaan toteuttaa, ja toisaalta siihen, miten tehokasta koodia kääntäjä tuottaa. Kääntäjän toteuttamisessa yksinkertaisimmat ominaisuudet voidaan yleensä toteuttaa tehokkaimmin. Toisin sanoen vaikeasti toteutettavat ominaisuudet ovat yleensä sellaisia, joita myös ohjelmoijan on vaikea ymmärtää ja käyttää. 1.7 Ohjelmointikielen toteuttaminen Ohjelmointikielen suunnittelu on pohjana kielen toteuttamiselle, mikä tapahtuu kääntäjän tai tulkin avulla. Kääntämisessä lähdekoodi käännetään sellaiseen muotoon, että kone pystyy sen suorittamaan. Kuva 1.2 esittää käännösprosessia. Kun ohjelma on käännetty, ohjelmoija voi ajaa ohjelmaa eli antaa sille syötearvot, jolloin ohjelma tehtävän suoritettuaan antaa jonkin tuloksen (tulosarvot). Varsinainen kääntäminen sisältää useita vaiheita, joissa tehdään vaiheelle ominaisia tarkistuksia tai analyyseja. Nämä vaiheet ovat nimeltään: leksikaalianalyysi (selaaminen) syntaksianalyysi (jäsentäminen) semanttinen analyysi välikielen generointi koodin optimointi

Johdanto 27 Lähdeohjelma Kääntäjä Kohdeohjelma Syöte Kohdeohjelma Tulos Kuva 1.2: Käännösprosessi Lähdeohjelma Syöte Tulkki Tulos Kuva 1.3: Tulkkausprosessi koodin generointi. Kääntäjän toteuttamisessa kussakin analyysissa tehdään erilaisia tarkistuksia, jotta huomataan mahdolliset virheet lähdeohjelmassa. Kääntäjän toteutus sisältää yleensä symbolitaulun, johon kerätään ohjelmassa esiintyvät tunnukset kuten muuttujien, vakioiden, tyyppien ja aliohjelmien nimet. Nimet lisätään symbolitauluun silloin, kun ollaan käsittelemässä esittelyitä. Tässä yhteydessä tarkistetaan, ettei symbolitaulussa ole jo samalla näkyvyysalueella samannimistä tunnusta. Tätä kutsutaan esittelyiden työstämiseksi (elaboration). Kun taas jokin tunnus havaitaan ohjelmassa myöhemmin, tarkistetaan, löytyykö tunnus symbolitaulusta (eli onko tunnus esitelty) ja minkä tyyppinen tunnus on kyseessä. Vaihtoehtona ohjelmien kääntämiselle on ohjelmien tulkkaus, joka on esitetty kuvassa 1.3. Tulkkaaminen on kääntämiseen verrattuna hyvin yksinkertaista, ja sen etuna on virheiden helppo paikallistaminen. Jos esimerkiksi havaitaan taulukon indeksirajan ylittäminen, voidaan virheilmoituksen lisäksi antaa virheen tarkka sijainti lähdekoodissa. Haittoina ovat kuitenkin suorituksen hitaus ja suurempi muistitilan tarve. Ohjelmointikielistä Lisp on tulkkikieli, tosin myöhemmille versioille on toteutettu myös kääntäjiä. Myös monet skriptikielet, kuten JavaScript ja Python ovat tulkattavia. Tulkkaamisessa syötearvot annetaan sitä mukaan, kun tulkkaaminen etenee sellaiseen vaiheeseen, että syötearvoja tarvitaan. Kielissä voi olla piirteitä, joiden toteuttaminen tulkkaamalla on helpompaa ja luontevampaa kuin kääntämällä. Esimerkiksi Lisp-kielisessä ohjelmassa voidaan kirjoittaa koodia ja suorittaa tämä koodi saman tien.

28 Ohjelmointikielet Periaatteet, käsitteet, valintaperusteet Lähdeohjelma Muunnin Välikielinen ohjelma Välikielinen ohjelma Syöte Virtuaalikone Tulos Kuva 1.4: Käännösprosessin ja tulkkausprosessin välimuoto Kääntämisen ja tulkkaamisen hyvät puolet voidaan yhdistää, jolloin tuloksena saadaan kuvan 1.4 mukainen välimuoto. Kuvassa olevan muuntimen englanninkielinen vastine (translator) tarkoittaa sekä kääntäjää että tulkkia. Muunnin suorittaa tavallisesti kääntämisen alkupään vaiheita kuten leksikaali- ja syntaksianalyysit sekä välikielen generoinnin. Esimerkiksi Java-toteutuksissa tavukoodi on tällainen välikielinen esitys, jota sitten tulkataan. Tosin suuremman nopeuden saavuttamiseksi tavukoodi voidaan myös kääntää binaarikoodiksi JIT-kääntäjällä (Just-In-Time) juuri ennen ohjelman suorittamista. Käännetty koodi (esimerkiksi operaation koodi) voidaan tallentaa, jolloin operaatiota uudelleen kutsuttaesssa voidaan käyttää jo käännettyä koodia. Kääntämisen ja tulkkaamisen ero ei välttämättä ole kovin suuri. Jos kuvan 1.4 muunnin on hyvin yksinkertainen, prosessi on lähellä tulkkaamista. Jos taas muunnin on monimutkainen ja käy läpi kielen rakenteet läpikotaisin, eikä tee vain pientä mekaanista muunnosta, prosessi muistuttaa kääntämistä. Välikielinen esitys voi olla myös jokin ohjelmointikieli. Esimerkiksi jotkin Eiffel-toteutukset generoivat (ei-luettavaa) C-kieltä, joka sitten käännetään C-kääntäjällä. On myös mahdollista, että kielelle on olemassa sekä tulkki että kääntäjä. Tällöin ohjelmia kehitettäessä (ja virheiden etsinnässä) käytetään tulkkia. Kun ohjelma saadaan vakaammaksi, se käännetään paremman suoritusnopeuden saavuttamiseksi. Joissakin kielissä, esimerkiksi C:ssä ennen varsinaista käännöstä tehdään esikäännös, jonka yhtenä tehtävänä on laventaa makrot. Tämä on tarpeellista, koska makroja voidaan käyttää kielen syntaksin uudelleenmäärittelyyn: #define begin { #define end } #define then...