Programozási paradigmák

A szoftverfejlesztés t̶i̶t̶k̶o̶s̶ tanai

2017. június 22. - Indie Crawford
Babylon 5, a Szürkék Tanácsa

A programozási paradigmák, a szoftverfejlesztés misztikus tanai közé tartoznak. Akár a mágia különböző formái, ezek is más-más megközelítéseit jelentik a számítógépek kódolásának. Bár elég jól dokumentált, komoly irodalommal bíró fogalmakról van szó, pontos definíciókról mégsem beszélhetünk, ahogy teljes elkülönülésük, vagy precíz hierarchiába való rendezésük is álom csupán. Mégis, ahogy a fantázia-világok mágusai, úgy a programozóból mérnökké váló tanoncok sem kerülhetik meg a programnyelvek mögött megbúvó, már-már a filozófia, de legalábbis a matematika húrjait pengető különböző aspektusok tanulmányozását. Lehet ugyan valaki élelmes tűzvarázsló webfejlesztő, mindenre elszánt boszorkánymester rendszergazda, vagy netán etikus paplovag hacker, csak a mélyebb tanok megértésével válhat valaki teljes értékű játékossá, esetleg mesterré a szoftverfejlesztésben.

Liu Kang a Mortal Combatból

Egy-egy programnyelv fontos attribútuma, hogy mely paradigmát, vagy paradigmákat támogatja. Akadnak nyelvek, amelyek eszköztára többől is válogat, a programozóra bízva ezek vegyítését, míg mások egy bizonyos koncepció köré szerveződve rákényszerítik használójukat az alkotóik által kívánatosnak tartott útra. Ez nem feltétlenül rossz dolog. Egy-egy problémakör más-más hozzáállást követelhet meg a hatékony és elegáns megoldás megtalálásához, bizonyos minták követése pedig sok sikertelenségtől kímélhet meg minket. A programozási paradigmák minél szélesebb körű ismerete elengedhetetlen fegyvertény a szoftverfejlesztésben, tanulmányozásuk pedig látókörtágító és szórakoztató is egyben. A teljesség igénye nélkül, és ingoványos talajon kelve át ugyan, de összegyűjtöttem párat. A kiegészítéseket, kommenteket, építő kritikát, mint mindig, most is örömmel veszem!

Objektum-orientált programozás

Az objektumorientált programozás olyan, mint a Mozaik Mágia

Bár az elgondolás már az az 1967-es Simula nyelvben is fellelhető volt, majd később a Smalltalk nyelvben teljesedett ki, de az objektum-orientált paradigma a 90-es évektől vált igazán elterjedtté, sőt valósággal meghatározta a korszakot. A könyvespolcok az objektumorientált programozás rejtelmeit firtató művekkel voltak tele, élükön a szoftver-tervezésben alapműveltségnek számító, újító szellemű kiadvánnyal, a Gang of Four szerzőcsapat által jegyzett, Programtervezési Minták c. könyvvel. Boldog-boldogtalan a Delphi és persze a C++ rejtelmeibe ásta bele magát ekkor, és a lelkesedés a mai napig tart: a legtöbb ma is használt nyelv, mint pl. a Java, a PHP, a C#, a már említett C++, vagy a Python, a Ruby illetve a JavaScript is magáévá tette ezt a látásmódot.

Az objektum-orientált programozásnak (röviden: OOP) több sarokköve is van. Először is nélkülözhetetlen, az adatok és a rajtuk elvégzett műveletek egységbe zárása. Például egy pont kirajzolásához szükséges koordináta, valamint a pontot ez alapján megjelenítő utasítássor nem csak úgy lebegnek a program összes többi alkatrészével egy térben, hanem elkülönülve megalkotnak egy pont-objektumot, mely magában foglalja mindezeket. A szélesebb értelemben vett rendszer pedig efféle objektumok interakciójaként fogható fel. Akár csak a valóságban, már ha a „pont” és a „vonal” fizikai létezők, nem pedig a matematikus kivetítései lennének csupán... 

Az objektumok újabb objektumok építőkockáivá is válhatnak az öröklődés által, ami szintén egy fontos eleme a megközelítésnek. Az öröklődés segítségével az objektumok újrahasznosíthatják egymás jellemzőit. Ha van például egy generális, Humanoid névre hallgató objektumunk, akkor egy specializáltabb eset, mint például a Hobbit megörökölheti a Humanoidot megvalósító kódot, kiegészítheti vagy módosíthatja azt a saját igényei szerint. (Mondjuk a "két láb" tartalmú változót, "két szőrös talp"-ra cserélheti, a sétálást megvalósító kód mellé pedig egy sör-tánc metódust is iktathat.)

A történet persze nem ér véget itt, a látásmód ereje pedig könnyen belátható: az elvont problémákat a mindennapi életben megszokott módon modellezve könnyebbé teszi az azokról való gondolkodást. Ha egy egyszerűbb játék megírásához szükséges kismillió, a számítógéppel elvégeztetendő feladatra gondolunk, talán egyből el is megy a kedvünk az egésztől, ám ha objektumokra bontva közelítjük meg a problémát, máris átláthatóvá, és részenként megoldhatóvá tehetjük azt. Strukturálja tehát a gondolkodásunkat és a kódunkat is, nagyban megkönnyítve ezáltal a fejlesztést.

Kétélű fegyverről beszélünk azonban, túlzásba víve hajlamosak lehetünk túl sokféle objektum és túl összetett hierarchia létrehozására, megfeledkezve az egyéb paradigmák nyújtotta lehetőségekről. Ilyenkor az objektumok nyújtotta hatalom könnyen a mestere ellen fordulhat.

A túl széles objektum-hiearchia veszélyes lehet, akár Hobbitra a tüzijáték

Funkcionális programozás

Gordon Freemen (Half-Life) jelképe, a görög betű, a λ(lambda) az informatikában is jól ismert
A görög λ(lambda) programozó-jelkép... Is!

A funkcionális programozás ínyenceknek való tudomány. MÁGUS-os hasonlattal élve, ha az OOP a varázshasználókat jelenti, akkor a funkcionális programozás minden bizonnyal a harcművész kasztnak feleltethető meg. Míg az OOP-t alkalmazó fejlesztő nagy gonddal szövögeti az objektum-hierarchia hálóját, addig a funkcionális programozó a Pszí Lambda összpontosításával, villámgyorsan és egyenes úton jut el a célba.

Alapvetően matematikai megközelítésről lévén szó, ez az aspektus komoly elméleti megalapozottsággal bírt már a számítástechnika fejlődésének korai szakaszában is. A funkcionális paradigma nem az adatok és a műveletek egységbe zárásáról és a kézzelfogható világ dolgaihoz hasonló absztrakciókról szól, helyette a programot az adatokon végzett transzformációk sorozataként fogja fel. A funkcionálisan megírt szoftver tehát sokkal inkább folyamat, mint rendszer.

Ezeket a transzformációkat függvényekként jellemezhetjük, amelyek az adott bemeneti paraméterek kimeneti paraméterekké való alakítását írják le. Fontos jellemzője a megközelítésnek hogy az adatok megváltoztathatatlanok, új értékek generálódnak minden műveletnél, a régiek pedig nem íródnak fölül. Tisztán funkcionálisan gondolkodva az egyes függvényeken kívüli térben nincsenek egyéb, globális értéktárolók, amiket a függvények lekérdezhetnek vagy beállíthatnának – ellentétben az objektumorientált modellel, ahol az objektumok szabadon hozzáférhetnek a globális térhez, valamint egymás belső adatait is módosíthatják. A függvények egymással csupán bemeneti és kimeneti szinten kommunikálnak, a program futása nem függ valamilyen külső állapottól, adott paraméterekkel, mindig ugyanazokat az értékeket produkálja. Funkcionális nyelven úgy mondjuk, nincsenek mellékhatások, valamint a program állapot nélküli és tiszta.

A funkcionális programozó, akár Liu Kang a harcművész

Ez a tiszta működés teszi olyan kívánatossá a funkcionális programozást, ugyanis az így írt programok könnyen átláthatók, tesztelhetők, megbízhatóbb kód készítésére ösztönzik a fejlesztőt. Az egyéb külső állapotoktól való függetlenség miatt kifejezetten alkalmasak az olyan párhuzamos/konkurens/elosztott rendszerek kivitelezésére, mint a hálózati kommunikációt igénylő alkalmazások, vagy a több processzormagot kihasználó szoftverek. Nem csoda, hogy napjainkban komoly reneszánszát éli ez a megközelítés, valamint, hogy a legtöbb modern nyelv az objektumorientált mellett funkcionális lehetőségeket is támogat. Fontosabb funkcionális, vagy jelentős részben funkcionális programnyelvek például a Haskell, az Erlang, a Lisp-nyelvek, az F# vagy a Scala.

Logikai programozás

Tuvok, a vulkáni (Star Trek: Voyager), a logika felkent papja

A logikai programozás a szokványos programozási mintáktól merőben eltérő megközelítés. A funkcionális paradigmával ugyan rokonítható (mindketten a Nagy Lambda gyermekei), de míg fivére a funkciók fegyverével vágja át a gordiuszi csomót, addig emez a puszta logika segítségével csap le, vagy inkább következtet ki. Az adatok ekkor, mint tények, állítások és a rájuk vonatkozó szabályok vannak jelen.

A programozónak nincs más dolga, mint definiálni egy ilyen logikai rendszert, ezek után pedig kérdéseket szegezni neki. Ha tudjuk például, hogy Tyrion egy Lannister, valamint hogy a Lannisterek mindig megfizetik az adósságukat, akkor a rendszer könnyedén ki tudja következtetni a tényt, hogy Tyrion sosem marad adós.

Egy Lannister mindig megfizeti az adósságát

A logikai programnyelvek, mint pl. a Prolog, az ASP, vagy a Datalog ennél jóval összetettebb problémák megoldására is képesek. A fentiekből (és még néhány ezer tényből és szabályszerűségből) talán már kikövetkeztethető, hogy a mesterséges intelligencia kutatásban, azon belül is első sorban az úgynevezett szakértői rendszerek kifejlesztésében jutott elévülhetetlen szerep ennek a paradigmának.

Egy széleskörű ismereteket tartalmazó adatbázis felállításával a logikai következtetés igen ütőképes lehet. Orvosi vagy technikai diagnosztika, természeti erőforrások felkutatása, sőt bűntények megoldása is a lehetséges alkalmazási területek közé esik. Nyelvi rendszerekkel való kísérletezésre éppúgy alkalmas, mint adatbázisokkal való munkára. Deklaratív volta, lekérdezés alapú működése olyan elterjedt programnyelvekre is hatást gyakorolt, mint például az SQL, mely a világinternet jelentős részének adatbázis igényeit elégíti ki, köztük vélhetően ezen blogét is.

Metaprogramozás

A metaprogramozás, ugyan nem a SkyNet maga (Terminátor), csak majdnem...

A metaprogramozás fantasztikus elemek nélkül is könnyen beindíthatja képzeletünket, hiszen voltaképpen a program azon képességéről beszélünk, hogy az megváltoztathassa saját kódját. Élő szövet a belső fémvázon, vagy legalábbis majdnem. Többféle módszer is létezik ennek elérésére. Jellemzően az adott nyelv a saját programkódját is, mint adatot képes kezelni, így aztán a programon belülről tudjuk manipulálni és futtatni annak részeit. Ez azon kívül, hogy sok izgalmas, filozófiai és művészeti lehetőség előtt tárja szélesre a kaput, szakavatott kézben villámgyors eszközt is jelent bizonyos problémák megoldására. Bár a program jó eséllyel válik nehezebben értelmezhetővé, magának a nyelvnek új funkciókkal való kiegészítése, vagy a program kódjának futás közben történő megváltoztatása könnyen lerövidítheti a megoldása felé vezető utat.

Motoko Kusanagi Őrnagy (Ghost in The Shell / Páncélba zárt szellem )

Jól érzékelteti továbbá a metaprogramozás jelentőségét, hogy legkorábbi megvalósítása Lisp nyelven történt. Ez a lassan 60 évessé váló, a mai napig is nehezen megkerülhető, sok más nyelv tervezésére is hatást gyakorló programnyelv többek között a mesterséges intelligencia kutatások fontos eszköze.

A metaprogramozás különböző formái több nyelvben is megtalálhatóak, a funkcionális Erlang, a Haskell, az OCaml vagy a Lisp különböző változatai (Scheme, Common Lisp, Clojure) illetve az objektum-orientált paradigmát teljes mértékben támogató Ruby is jó kiindulási pont lehet. Sőt a logikai Prolog is támogatja a tudás-bázis futásidejű módosítását, ami szintén a metaprogramozás egy formája.


Nem is az informatika tudományának óceánjába, a víz fölött áramló levegőbe pacskoltunk csupán csak bele a fentiek által. Jó lett volna még beszélni a programozási paradigmák főbb, általánosabb ágairól is, mint amilyen az imperatív és deklaratív vonal, és persze sok más fontos és érdekes megközelítés is akad. Konkurens, esemény-vezérelt, tömb-központú, rekurzív – hogy csak néhányat említsek , melyek közül nem egy szervesen kapcsolódik a fent tárgyalt esetekhez is. Elég nehéz témába vágtam ugyanakkor, próbáltam óvatosan egyensúlyozni a hozzáértőbb és a lelkesen érdeklődő olvasóim igényei között. A fentiek jelenthetnek ugyanakkor némi kapaszkodót további programozással és programnyelvekkel foglalkozó írásokhoz, írásaimhoz is. Jutalmul, hogy végigolvastad ezt a bejegyzést, amely minden mesterkedésem ellenére is kikandikál a bulvárból, íme a rollerező Peter Dinklage a.k.a. Tyrion Lannister!   

Peter Dinklage, a Tyrion-t játsztó színész egy rolleren :)

A bejegyzés trackback címe:

https://indiecrawford.blog.hu/api/trackback/id/tr2812604801

Kommentek:

A hozzászólások a vonatkozó jogszabályok  értelmében felhasználói tartalomnak minősülnek, értük a szolgáltatás technikai  üzemeltetője semmilyen felelősséget nem vállal, azokat nem ellenőrzi. Kifogás esetén forduljon a blog szerkesztőjéhez. Részletek a  Felhasználási feltételekben és az adatvédelmi tájékoztatóban.

goumideaumineaux 2017.06.22. 20:08:25

Köszi ezt az összefoglalót. Nagyon tetszett.
"Jó lett volna még beszélni a programozási paradigmák főbb, általánosabb ágairól is, mint amilyen az imperatív és deklaratív vonal, és persze sok más fontos és érdekes megközelítés is akad. Konkurens, esemény-vezérelt, tömb-központú, rekurzív – hogy csak néhányat említsek –, melyek közül nem egy szervesen kapcsolódik egyes, fent tárgyalt esetekhez is."
Én már várom a következő írásod a fent említett témában.

Dzsontra Volta 2017.06.22. 20:20:48

A c# és a Transact-SQL véletlenül maradt ki :) ?

Indie Crawford · http://indiecrawford.blog.hu 2017.06.22. 20:48:21

@goumideaumineaux: Köszi! :)

@Dzsontra Volta: A C# igen, javítva, köszi! A T-SQL-ről pedig keveset tudok, de valóban témába vág, utána is olvasok, köszi a meglátást!

Alick 2017.06.23. 09:41:57

Érdekes lenne olvasni a reverse engineering mai lehetőségeiről is.

Serrin 2017.06.23. 10:07:59

Remek írás és ebből jöttem rá, hogy mostanában JS-ben egyre inkább funkcionálisan programozok.

endike · http://barathendre.wordpress.com/ 2017.06.23. 10:08:53

végül úgyis mindenből gépi kód lesz, sok-sok "goto"-val :D

Indie Crawford · http://indiecrawford.blog.hu 2017.06.23. 12:19:09

@Serrin: Jaja, a JS eddig sem vette félvállról a funkcionális dolgokat, az ES6-tal meg jött még pár nyalánkság. Mivel az objektum-orientáltság kizárólag protoípikus (volt az ES6 előtt), kicsit nehezen volt hozzáférhető, ellenben pl. az első osztályú funkciók mindig is ott voltak, jelezve a fő csapásirányt.

@endike: Van amiből bytecode lesz! :P

Serrin 2017.06.23. 12:25:40

@Indie Crawford: ES6-ban a class csak egy réteg a prototype oop felett, vagyis nem valódi osztályokról van szó. Próbáld ki ezt a kódot egy konsolban:

class class1 { } console.log(typeof class1);

Az eredmény a "régi jó" function lesz.
Ettől még ez segítség a klasszikus öröklést ismerőknek.

Serrin 2017.06.23. 14:52:38

@Indie Crawford: Kimaradt, hogy jó ez a blog. :)

Indie Crawford · http://indiecrawford.blog.hu 2017.06.23. 15:42:28

@Serrin: Túl jók vagytok hozzám! :)

(Igen, tudom, hogy csak syntactic sugar, de mégis egy lépés az osztályok fel... De talán nem voltam pontos, igaz! :)

eMeL · http://emel.blog.hu/ 2017.06.26. 10:52:14

Nemrég olvastam egy könyvet a Thinking in LINQ-t.

Ott hívja fel a szerző a figyelmet, hogy a linq tulajdonképpen funkcionális programozás a C#on belül :)

Gerusz · http://gerusz.blogspot.com 2017.10.09. 16:46:35

@eMeL: A C# a Linq-n kívül is sok helyen támogatja a funkcionális programozást, a Func<T_input, T_output> delegate típus (0-tól 8 inputig megvannak a definíciói) más helyen is használható.

A Linq-hoz hasonló funkciót lát el (bár kicsit körülményesebben) a Java 8 Stream API. Továbbá a Java 8+ arra is lehetőséget ad, hogy egyetlen függvénnyel rendelkező interfészeket és absztrakt osztályokat lambdával, vagy metódus-hivatkozással helyettesítsünk. (Asszem, ez a C#-ban is lehetséges.) Szerintem nem kell magyaráznom, mennyivel letisztultabb kódot eredményez, főleg GUI-k esetén (pl. nem kell egy komplett anonim osztályt deklarálni eseménykezeléshez).

christo161 · http://itkezdoknek.blog.hu/ 2017.10.13. 16:25:15

Hát igen, jó lenne egy hosszú cikket olvasni arról, hogy pontosan mitől imperatív egy programozási nyelv vagy pontosan mitől strukturált.
Egyébként a deklaratív és funkcionális viszonylag rokon fogalmak, jól tévedek?
Illetve gondolom tudjátok, hogy C++-ban is van már lambda kalkulus.
süti beállítások módosítása