TIE-20200 Ohjelmistojen suunnittelu Luento 13 : Lambdat sun muut TIE-20200 Samuel Lahtinen Matti Rintala 1
Ohjelmassa tänään Lambdat, templatet, genericsit, luokkatason funktiot Erilaisia tapoja nähdä ohjelman rakenne: Entity component system Funktionaaliset jutut
Moniperinnästä Käyttäjäkohtaiset rajapinnat (esimerkki), mikä SOLID-periaate?
Entity Component -juttu Ohjelman rakenne System-osassa ohjelman toiminnallisuusosat Esim. pelissä pelilogiikka, grafiikan piirto, fysiikkamoottori, törmäystarkastukset jne. Entity: yleiskäyttöinen objekti/olio/käsite, tunniste pelissä oleville asioille (sisältää tunnisteen, esim. int) Component: Tiedot jollekin tietylle käsitteelle yhdestä systemnäkökulmasta, data (joissain tapauksissa tarvittava toiminnallisuus) System-osien välillä tiedotusta esim. tapahtumien avulla Yleisin käyttökohde pelit (myös valmiit pelimoottorit/peli/ohjelmistokehykset (frameworkit)) Tiettyyn tarkoitukseen sopiva korkean tason suunnittelupäätös/arkkitehtuuritason ratkaisu, vaatii asioiden tekemistä normaalista suunnittelusta poikkeavalla tavalla
Entity Component juttuun liittyen Jos pelien kanssa askartelu kiinnostaa, kannattanee vilkaista esimerkkejä ECS-lähestymistapaan liittyen: http://www.gamedev.net/page/resources/_/technical/game-programming/understandingcomponent-entity-systems-r3013 http://gameprogrammingpatterns.com/component.html http://www.richardlord.net/blog/what-is-an-entity-framework
Erittäin kevyesti funktionaalisista kielistä ~Kaikki esitetään funktioina, niiden parametreina & paluuarvoina Jotain funktionaalisuuteen liittyviä piirteitä: Sivuvaikutuksettomuus: funktion tulos aina sama samoilla parametreilla kutsuttuna Funktiot parametreina, paluuarvoina, korkeamman luokan funktiot Rekursion käyttö (ei silmukkamuuttujaa) Immutable data yleistä: tieto luonnin jälkeen muuttumatonta Epäpuhtauksia: tilamuuttujia, sivuvaikutusten salliminen (käyttöliittymään liittyvät asiat, tietojen syöttö/tulostus) Funktionaalisuuden tunkeutuminen normikielien (imperatiivisten) puolelle, esim. funktiot parametreina, lambdat, yms. rakenteet
Erittäin kevyesti funktionaalisista kielistä Johdantoa funktionaalisuuteen, Lambdat jne. Kannattaa tutustua johonkin funktionaaliseen kieleen: Yleissivistyksen kannalta hyvä Hyvä ajatustapaharjoitus Trendikästä teollisuudessa Pääsee eksklusiivisen kerhon jäseneksi ja voi päteä sanoilla kuten monadi, s-expression, atoms, catamorphism, puhdas funktio, higher order function, currying Helpompi hämätä asiakkaita jne., osa ymmärtää jo tavallisen koodin päälle Työkalut ja menetelmät osin epäkypsiä
Funktionaalisuudesta Kieliä: Haskell, Clojure, Lisp(Scheme) olio-funktionaalinen hybridit: Scala, F#, kummalliset: Erlang, R Lisäluettavaa kiinnostuneille Kurssimateriaalit: http://www.cs.tut.fi/~bitti/functional-seminar/ http://www.cs.tut.fi/~okp/2011/luentomatsku-pdf/funktio.pdf http://www.cs.helsinki.fi/u/mnykanen/fop/ http://www.mit.jyu.fi/opiskelu/seminaarit/bak/funktion/ http://www.cs.hut.fi/~cessu/fp-sem/ Online tutoriaaleja: http://tryhaskell.org/ http://www.tryclj.com/ http://java.ociweb.com/mark/clojure/article.html
Lambdat, C++11: miksi? Tarve välittää kirjastolle/funktiolle toiminnallisuutta callback-funktiot Geneeristen kirjastojen räätälöinti STL:n algoritmit (esim. find, sort) Koodin rinnakkainen suoritus Aiemmin sama mahdollista funktio-osoittimilla ja funktio-olioilla Kömpelömpiä, määrittely käyttökohteesta irrallaan
Funktio-osoittimista Funktio-osoittimet funktionaalisten kielten tyylisiä Parametrit ainoa data sisään, viiteparametrit & paluuarvo datana ulos bool alle5( int a ) return a < 5; } std::find_if( v.begin(), v.end(), &alle5 ); Muun datan välitys vaikeaa (lähinnä globaalit muuttujat) int raja; // Oltava globaali muuttuja! bool allerajan( int a ) return a < raja; } std::cin >> raja; std::find_if( v.begin(), v.end(), &allerajan );
Lambdat Lambdat ikivanha keksintö (Lisp) C++11:n lambdoissa kuitenkin joitain eroja Idea: Lambdat ovat funktion kaltaisia tuotteita, ottavat parametreja, palauttavat paluuarvon nimettömiä, määrittelemättömän tyyppisiä (melkein) pystyvät viittaamaan luontiympäristönsä muuttujiin, samoin muuttamaan pystyvät kopioimaan itseensä osia luontiympäristöstään
Lambdojen syntaksi []}() C++11:n syntaksi lambdalle [ympäristö](parametrit)->paluutyyppirunko;} Ympäristö tyhjä, jos ei viittaa ympäristöönsä parametrit tyhjä, jos ei parametreja (myös () sulut voivat puuttua) jos ->paluutyyppi puuttuu, se on void, paitsi jos runko pelkkä return-lause, jolloin päätellään
Lambda-esimerkkejä Lambda (std::sort-funktion parametriksi) std::sort( stuff.begin(), stuff.end(), // Lambda alkaa [](float a, float b) return (std::abs(a) < std::abs(b)); } // lambda loppuu ); std::find_if( v.begin(), v.end(),[]( int a ) ->bool return a<5;} ); std::find_if( v.begin(), v.end(),[]( int a )return a<5;} ); int raja; // Paikallinen muuttuja std::cin >> raja; std:find_if( v.begin(), v.end(), [raja]( int a )return a<raja;} );
Ympäristöön viittaaminen Lambda voi viitata paikallisiin muuttujiin: lambdan elinaika vs muuttujan elinaika! C++:n ongelma, muuttujilla määrätty elinaika (C++:n muistimalli) Toinen tapa ajatella: lambda saa parametreja ympäristöstään luontihetkellä, normaalit parametrit kutsuhetkellä Kaksi tapaa: arvon ja viitteen välitys
Ympäristön kopioiminen Tapa 1: ympäristön kopioiminen = (oletus) Käytetyt muuttujat kopioidaan lambdan sisään (vrt. arvoparametri), elinkaaret eivät ongelma Kopioita voi muuttaa vain, jos lambda on mutable int raja; std::cin >> raja; std::find_if( v.begin(), v.end(), [=raja](int a)return a<raja;} ); std::for_each(v.begin(), v.end(), [raja](int a) mutable std::cout << ++raja; });
Ympäristöön viiteviittaaminen Tapa 2: Ympäristöön viittaaminen & Lambda käyttää suoraan ympäristön muuttujia Ohjelmoija vastaa, että muuttujat pysyvät elossa int summa = 0; std::for_each(v.begin(), v.end(),[&summa](int a)summa += a;});
Ympäristöön viittaaminen Ympäristöön viittauksia voi yhdistellä int raja = 5; int vertailuja = 0; std::find_if( v.begin(), v.end(), [=raja,&vertailuja](int a)->bool ++vertailuja; return a<raja; } ); Implisiittinen ympäristöön viittaus (valittava, onko viite vai kopio) int raja = 5; int summa = 0; std::find_if( v.begin(), v.end(), [=](int a)return a<raja;} ); std::for_each( v.begin(), v.end(), [&](int a)summa+=a;} );
Ympäristöön viittaaminen, luokissa [this]()} jäsenfunktiossa määritelty lambda voi viitata jäsenmuuttujiin ja -funktioihin class X int i_; void g( int x ); void f( std::vector<int> const& v ) std::for_each( v.begin(), v.end(), [this](int a) i_+= a; g(a); } ); } };
Funktioon viittaus Fibonaccin sarjaa, esimerkkinä funktion antaminen ympäristönä std::function<int(int)> recursivefibonacci = [&recursivefibonacci]( int n ) return n < 2? 1 : recursivefibonacci( n-1 ) + recursivefibonacci( n-2 ); };
Auton käyttöä, paikallinen funktio lambdalla Lambdan kutsu määrittelyn jälkeen ei onnistu, ei nimeä, ei mitä kutsua C++11 sallii lambdojen tallentamisen myös nimettyihin muuttujiin voidaan kutsua samaa lambdaa useampaan kertaan int kerronta( std::vector<int>& v, std::list<int>& l ) int x=1; auto kerro = [&](int i)x *= i;}; // lambda talteen for( auto i: v ) kerro( i ); } // käyttö vektorilla for( auto i: l ) kerro( i ); } // käyttö listalla return x; }
Lambdan toteutus? Yksi mahdollisuus: lambdat funktio-olioita Nimettömiä luokkia, joissa kutsuoperaattori operator() Jäsenmuuttuja jokaista ympäristöviittausta kohti (viite, jos viittaus ympäristöön) Kääntäjä saa myös toteuttaa miten haluaa Esim. funktio-olio, jossa suoraan aktivaatiotietueen osoite yms.
C# lambdat sun muut Tukee lambdoja, käytössä myös LINQ (Language-Integrated Query) Käyttö kuten C++:ssa, public partial class Form1 : Form customers.where(c => c.city == "London"); public Form1() InitializeComponent(); button1.click += async (sender, e) => // ExampleMethodAsync returns a Task. await ExampleMethodAsync(); textbox1.text += "\r\ncontrol returned to Click event handler.\r\n"; }; } async Task ExampleMethodAsync() // The following line simulates a task-returning asynchronous process. await Task.Delay(1000); } } TIE-20200 Samuel Lahtinen http://msdn.microsoft.com/en-us/library/bb397687.aspx
C# LINQ-esimerkki class LINQQueryExpressions static void Main() // tietovarasto int[] scores = new int[] 97, 92, 81, 60 }; // hakulauseen määrittely IEnumerable<int> scorequery = from score in scores where score > 80 select score; // suoritetaan haku foreach (int i in scorequery) Console.Write(i + " "); } } } // Output: 97 92 81 http://msdn.microsoft.com/en-us/library/bb397933.aspx
Java & Lambdat (Java 8) Java 8:ssa tukea lambdoille (hieman rajatumpi kuin c++/c#) Arrays.sort(words, (first, second) -> Integer.compare(first.length(), second.length())); public static void repeatmessage(string text, int count) Runnable r = () -> for (int i = 0; i < count; i++) System.out.println(text); Thread.yield(); } }; new Thread(r).start(); } https://docs.oracle.com/javase/tutorial/java/javaoo/lambdaexpressions.html
Luokkatason jutut Luokkafunktiot, luokkamuuttujat Luokkatason toiminta, ei liity mihinkään olioon C++ ja muut vastaavat kielet: static avainsana Luokkavakiot, luokkatason vakioita, luettavissa, tarkistettavissa ilman instanssia (luodaan vain kerran jne.) Puhtaat oliokielet, kaikki olioita, luokat olioita, joista yksi instanssi, metatason jutut TIE-20200 Samuel Lahtinen 26
Luokkatason jutut Luokkafunktio voi käsitellä luokkamuuttujia, luokkavakioita, ei näe olioiden asioita (jäsenfunktiot, muuttujat) Luokkamuuttujat saavat alkuarvon ohjelman käynnistyksen yhteydessä TIE-20200 Samuel Lahtinen 27
Esimerkki, tehdashärveli, taas TIE-20200 Samuel Lahtinen
Operaattoreiden kuormittaminen Operaattoreita voi luoda omille tyypeille ja kuormittaa olemassa oleviakin Sijoitusoperaattori tuttu, saman voi tehdä muillekin tyypeille, esim. +,-,*,/,+=,<<,>>,<,>, Operaattorien määrittäminen jäsenfunktioina, voidaan tehdä omista tietotyypeistä perustietotyyppien kaltaisia Oikein tehtynä voi helpottaa & nopeuttaa koodausta ja tehdä koodista selkeämpää Väärin tehtynä loistava keino harhauttaa ja hämmentää TIE-20200 Samuel Lahtinen 29
Geneerisyys ja mallit (mallineet, sapluunat ) (generics & templates) Perintä ja polymorfismi: kantaluokkaosoittimen/viitteen päässä voi olla mitä tahansa kyseisestä luokasta perittyä Esim. funktiolle kelpaa mikä tahansa kantaluokan tyyppinen olio ja kantaluokan palveluja käytetään. Periytymissuhdevaatimus ei aina hyvä asia Mallin idea: Koodissa yksi tai useampi auki jätetty tyyppiparametri auki jätetty tyyppi kiinnittämällä saadaan todellista koodia ( instantioidaan malli) Malli on mahdollista instantioida useita kertoja useita samantapaisia koodeja, joissa vain tyyppiparametrin arvo eroaa TIE-20200 Samuel Lahtinen 31
Aihiot, mallineet Mallit mahdollistavat geneerisen ohjelmoinnin (generic programming), jossa monet asiat jätetään auki myöhemmin määrättäväksi Auki jätetyn tyypin käyttö koodissa määrää sen ominaisuusvaatimukset, esim. Sijoitus sijoitusoperaattori oltava Arvoparametrina kopiorakentaja oltava Jäsenfunktiokutsu ko. jäsenfunktio löydyttävä Mallia käytettäessä ominaisuusvaatimukset eivät toteudu (kryptinen) käännösvirhe Mallia suunniteltaessa hyvä minimoida ominaisuusvaatimukset (vaikeaa) TIE-20200 Samuel Lahtinen 32
Funktiomallit (function template) Malli funktioille, joissa tyyppejä (normaalisti parametreja) jätetty auki Listaus 9.5 s. 233 (9.5 s. 259): 1 template<typename T> // Tai template <class T> (identtinen) 2 T min(t p1, T p2) 3 4 T tulos; 5 if(p1 < p2) 6 7 tulos = p1; 8 } else 9 tulos = p2; 10 } 11 return tulos; 12 } Funktiomalli instantioidaan automaattisesti kutsuttaessa (mahdollista myös kertoa tyyppi ekplisiittisesti, esim. min<int>(3,4)): min(1,2) int min(int p1, int p2) min( a, e ) char min(char p1, char p2) TIE-20200 Samuel Lahtinen 33
Luokkamallit (class template) Malli luokille, joissa tyyppejä jätetty auki Itse malli ei ole luokka, vaan siitä tehdyt instanssit Luokkamallit instantioitava aina ekplisiittisesti: Jokainen instanssi oma tyyppinsä, jotka eivät ole keskenään yhteensopivia (char vs. int vs string ) TIE-20200 Samuel Lahtinen 34
Luokkamalli-esimerkki // hieno luokkamalli, joka laskee minkä tahansa olioiden/muuttujien // instanssien määrän template< typename T > class Laskuri public: Laskuri(): laskuri_()} ~Laskuri()} void lisaa( T stat ); void tulosta( std::ostream& out ) const; T annayleisin() const; private: typedef map< T, long > IRegister; typedef typename IRegister::const_iterator citerator; IRegister laskuri_; }; TIE-20200 Samuel Lahtinen 35
Luokkamallin toteutukset template< typename T > void Laskuri< T >::lisaa( T stat ) laskuri_[ stat ]++; } template< typename T > void Laskuri< T >::tulosta( std::ostream& out ) const out << "kutakin tyyppiä oli (tyyppi - määrä): " << endl; for( citerator it = laskuri_.begin(); it!= laskuri_.end(); ++ it ) out << "'" << it->first << "' - " << it->second << endl; } } template< typename T > T Laskuri< T >::annayleisin() const if( laskuri_.empty() ) throw std::out_of_range( "Laskuri on tyhjä" ); } citerator yleisin = laskuri_.begin(); for( citerator it = laskuri_.begin(); it!= laskuri_.end(); ++ it ) if( yleisin->second < it->second ) yleisin = it; } } return yleisin->first; } TIE-20200 Samuel Lahtinen 36
Luokkamallin käyttö int main() Laskuri< char > chars; cout << "kirjoittele jotain ja paina enter..." << endl; string temp; std::getline( std::cin, temp ); if( std::cin.eof() )std::cout<< "EOF!!!!" << endl;} for( unsigned i( 0 ); i < temp.size(); ++i ) chars.lisaa( temp.at( i ) ); } chars.tulosta( cout ); cout << "yleisin merkki oli: " << chars.annayleisin() << endl; cout << "Sama murtoluvuilla" << endl; Murtoluku m1( 5,6); Murtoluku m2( 5,6); Murtoluku m3( 5,9); Murtoluku m4( 1,6); Murtoluku m5( 5,9); Murtoluku m6( 5,12); Murtoluku m7( 5,6); Murtoluku m8( 2,3); Laskuri< Murtoluku > murtoluvut; murtoluvut.lisaa( m1 ); murtoluvut.lisaa( m2 ); murtoluvut.lisaa( m4 ); murtoluvut.lisaa( m5 ); murtoluvut.lisaa( m6 ); murtoluvut.lisaa( m7 ); murtoluvut.lisaa( m8 ); murtoluvut.tulosta( cout ); } TIE-20200 Samuel Lahtinen 37
Tyyppiparametrien vaatimukset Mitä ominaisuuksia mallin tyyppiparametrilla pitää olla? Malli ei kerro sitä eksplisiittisesti Ainoa vaatimus: mallin koodin on käännyttävä Ongelma: koodin vaatimukset vaikeasti näkyvissä Ohjeita geneeriseen ohjelmointiin: Tyyppiparametrien vaatimukset dokumentoitava selkeästi Vaatimusten määrä minimoitava yleiskäyttöisyyden lisäämiseksi Tiedettävä kääntäjän kulissien takana tekemän koodin vaatimukset (kopio- ja oletusrakentaja yms.) TIE-20200 Samuel Lahtinen 38
Yhteenveto Opittiin, että isot perintähierarkiat ja sun muut eivät aina ole oikea tapa lähestyä asioita Saatiin pakollinen mainosspämmi funktionaalisista kielistä Lambdat, funktionaalista ohjelmointia ei-funktionaalisella kielellä? (Mitä eroja puhtaaseen funktionaalisuuteen?) http://en.cppreference.com/w/cpp/language/lambda https://msdn.microsoft.com/en-us/library/dd293608.aspx http://www.umich.edu/~eecs381/handouts/lambda.pdf Kertauksena templatet, muistuksena yleiskäyttöisyydestä ja sen hyödyistä/vaaroista