Címkearchívumok: hibák

Programnyelvek és típusok

A Debra programnyelv tervezése közben természetesen előkerült a talán legfontosabb tervezési kérdés: szigorúan típusos, vagy dinamikus legyen a nyelv, esetleg a kettő keveréke? Erről már írtam korábban többször is, most újra összefoglalom a legfontosabb tudnivalókat.

Kétféle programnyelv van: a szigorúan típusos és a dinamikus típusos. Esetleg olyan megoldás is lehet, hogy az egyik típusba tartozó nyelv közeledni kezd a másikhoz és kevert nyelvi konstrukciókat alkalmaz (ilyen megoldás a „dynamic” típus bevezetése a C# nyelvbe).

Szigorúan típusos: deklarálni kell a változók típusát, vagy explicit módon vagy értékadással, ilyenkor az értékből derül ki a típus (nem egyértelműség esetén utótagokkal segíthetünk (L, F, … a C#-ban). Az osztályok mezői is szigorúan deklaráltak. A legfontosabb megkötés az, hogy a változó típusa nem is változhat, a változó élettartama idejére ugyanaz marad, más értéktípust nem rendelhetünk hozzá (kivétel float = int, string = char).

Az ilyen nyelvekben a függvény, eljárás definíciókban is meg kell adni a formális paraméterek típusát, függvények esetében a visszatérési érték típusát is. Ez nehézkesnek tűnhet a dinamikus nyelvekhez képest, de amikor többféle argumentumlistával rendelkező, ugyanolyan nevű függvényekről, eljárásokról van szó, a szigorú típusosság segíti a fordítót abban, hogy kiválassza a legjobban illeszkedő függvényt.

A dinamikus nyelvek legnagyobb előnye, hogy menet közben változhat egy-egy változó típusa, értékadásonként, és nem kell ezt eldönteni a definiálásnál, azaz kevesebb kódot kell írni, a kód rövidebb, áttekinthetőbb lesz. Ugyanakkor ez hátrány is lehet, amikor felüldefiniált (túlterhelt) függvényeket szeretnénk írni, ezt ugyanis dinamikus nyelv esetében nem tehetjük meg, csak akkor, ha különbözik az azonos nevű függvények argumentumszáma. Azonos nevű, egyenlő számú formális paraméterrel rendelkező függvények közül, a fordító nem tud választani, a különböző típusú paramétereket a függvény kódjában, futási időben kell megoldanunk. Egy alternatíva lehet, ha segítjük a fordítót, és adunk típust a formális paramétereknek, de ebben az esetben is futási időben dől el, hogy melyik függvény fog lefutni. Dinamikus nyelvek esetében úgy tűnik, a legjobb megoldás, ha az azonos nevű, azonos számú paraméterrel rendelkező függvények esetében a függvények nevében elhelyezünk valamilyen, az argumentumok típusára utaló jelzést, így a fordító fordítási időben is tud választani az azonos nevű függvények közül.

A legfontosabb különbség a kétféle nyelv között az, hogy szigorúan típusos nyelveknél a típus összeférhetetlenségi hibák már a fordítási időben kiderülnek, a dinamikus nyelveknél ezek a hibák a futás közben jönnek csak elő. Azaz, dinamikus nyelven sokkal könnyebb a programírás, de sokkal nehezebb a hibakeresés. Minden értékadás, függvényhívás egy-egy potenciális hibaforrás, statikus nyelveknél, ez mind előkerül a fordításkor.

Külön meg kell említenünk a tömbök esetét: statikus nyelveknél meg kell mondanunk, hogy a tömb milyen típusú elemeket tartalmaz, és minden tömb csak azonos típusú elemeket tartalmazhat. Dinamikus nyelveknél egy-egy tömb, különböző típusú elemeket is tartalmazhat ugyanabban a tömbben, ami jól jöhet, ha adatbázis rekordokat akarunk beolvasni gyorsan és egyszerűen egy kétdimenziós tömbbe, ahol a sorok olyan tömbök, amiknek az elemei különböző típusúak, az adatbázis egyes mezőinek megfelelően. A tömb feldolgozása viszont futási időben lassabb lehet, és a típusok egyeztetése futási hibákat okozhat a legváratlanabb helyeken.

Statikus nyelvekben is lehet olyan tömböket készíteni, amiknek nem azonos típusúak az elemei, de ekkor elveszítjük a szigorú típusosság minden előnyét.

Foglaljuk most össze, a kétféle nyelv tulajdonságait, különös tekintettel arra, hogyan lehet a kétféle megközelítést keverni, ezzel rugalmasabb nyelvet létrehozni.

Statikus nyelvek:

– minden változónak és függvény argumentumnak definiálni kell a típusát

– könnyen megvalósítható a függvény felüldefiniálás, az azonos nevű függvények közül a fordító választja ki a megfelelőt

– a tömbök csak azonos típusú elemeket tartalmazhatnak, a típust meg kell adni a tömb definiálásánál

– lassabb a kód írása, és az nagyobb is, nehezebben olvasható

– a típusegyezőségi hibák már fordítási időben kiderülnek, de a fordítás lassabb

– jóval kevesebb hiba fordulhat elő futási időben, egyszerűbb a hibakeresés, a futás gyorsabb

– akkor érdemes ilyen nyelvet használni, ha jól definiált a környezet, és a programot sokáig szeretnénk használni

Hogyan lehet dinamikus elemeket bevezetni: a legegyszerűbb, ha bevezetünk egy olyan típust, ami éppen azt jelzi, hogy az ilyen változó bármilyen típusú értéket tartalmazhat (ilyen a „dynamic” is) . A függvények is kaphatnak ilyen argumentumokat, és a tömbök is lehetnek ilyen típusúak, azaz az elemeik bármilyen típusú értéket felvehetnek. Természetesen ezzel elveszítünk minden előnyt, amit a statikus nyelvek biztosítanak, és minden típusellenőrzési művelet, futási időre marad. Nagyon nagy előnye ennek a módszernek, hogy csak akkor vesszük elő, ha tényleg szükség van rá. A fordítóban benne kell lenni a képességnek az ilyen kód kezelésére, de ha nem használjuk, a lefordított programban nem lesznek váratlan típus-kompatibilitási hibák, és a futás is gyors lesz.

Dinamikus nyelvek:

– minden változónak és függvény argumentumnak bármilyen típusú értéke lehet

– a függvény felüldefiniálás csak a különböző argumentumszámú függvények esetében lehetséges, egyébként a névben szerepeltetni kell valamilyen, az argumentumtípusra utaló megkülönböztető jelzést

– a tömbök különböző típusú elemeket is tartalmazhatnak ugyanabban a tömbben is

– gyorsabb a kód írása, az rövidebb, könnyebben olvasható

– a típusegyezőségi hibák csak futási időben derülhetnek ki, a fordítás gyorsabb, de a futás sokkal lassabb

– jóval több hiba fordulhat elő futási időben, nehezebb a hibakeresés

– akkor érdemes ilyen nyelvet használni, ha rugalmas a környezet, és a programot csak alkalomszerűen, esetleg csak egyszer szeretnénk futtatni

Hogyan lehet statikus elemeket bevezetni: a fordítottját kell tennünk annak, amit a statikus nyelveknél elmondtunk. Ha nem adunk meg típust, marad a dinamikus működés, ha a definiálásnál, a függvény argumentumok megadásánál típusnevet is szerepeltetünk, akkor a fordító már tudja ellenőrizni a típus-kompatibilitást fordítási időben is. Ha egy tömbnek határozott típust adunk, akkor a tömb csak ilyen elemeket tartalmazhat. Ha a függvény argumentumok határozott típusúak, akkor máris lehetséges az egyértelmű függvény felülírás.

Következtetés: mindkét típusú programnyelv bővíthető olyan eszközökkel, amelyekkel gyakorlatilag egyenértékű nyelvek hozhatók létre. Ha az elsődleges cél a gyors fejlesztés, akkor legyen a nyelv dinamikus, és egészítsük ki statikus elemekkel. Ha a cél a precízebb, kevesebb futási hibával fenyegető nyelv készítése, akkor az legyen statikus, és tegyünk később bele dinamikus elemeket. Nincs tehát kibékíthetetlen ellentét a kétféle nyelv között.

A Debra nyelv megvalósításához a gyorsabb haladás érdekében a dinamikus változatot fogom használni, ezzel gyorsan tudunk egyszer használatos programokat, prototípusokat, kísérleti programokat készíteni. Majd jöhetnek a statikus definíciók, amivel végül egy teljes értékű nyelv lehet a Debra, ami így már rendelkezni fog mindkét típus előnyeivel, és kiküszöbölheti a hátrányokat is, a programozó dönthet, hogy milyen módszerrel programoz, az eszköz tartalmazni fogja mindkét módszerhez az eszközöket.

Ezzel olyan nyelv jöhet létre, ami egyszerre rugalmas, gyors fejlesztésre alkalmas eszköz, másrészt precíz, hibamentes programozásra is alkalmas lesz.

Athena fotója a Pexels oldaláról