A programozás alapelvei: statikus és dinamikus gépelés. Programozási nyelvek gépelése A gépelés típusai

Ez a cikk azt a minimumot tartalmazza, amit a gépeléssel kapcsolatban tudnia kell, hogy a dinamikus gépelést ne nevezze rossznak, a Lisp-et gépeletlen nyelvnek, a C-t pedig erősen gépelt nyelvnek.

BAN BEN teljes verzió minden típusú gépelésről részletes leírás található, kódpéldákkal fűszerezve, a népszerű programozási nyelvekre mutató hivatkozásokkal és szemléltető képekkel.

Azt javaslom, hogy először olvassa el a cikk rövid változatát, majd ha kívánja, a teljeset.

Rövid változat

A gépelési programozási nyelvek általában két nagy táborra oszlanak - a gépelt és a gépeletlen (nem gépelt). Az előbbibe a C, a Python, a Scala, a PHP és a Lua, míg az utóbbiba az assembly nyelv, a Forth és a Brainfuck tartozik.

Mivel a "gépelés nélküli gépelés" eleve olyan egyszerű, mint a parafa, nincs további típusokra osztva. A gépelt nyelvek azonban több, egymást átfedő kategóriába sorolhatók:

  • Statikus / dinamikus gépelés. A statikus értéket az határozza meg, hogy a változók és függvények végső típusai fordítási időben kerülnek beállításra. Azok. már a fordító 100%-ig biztos benne, hogy melyik típus hol van. A dinamikus gépelésben minden típus futási időben kerül meghatározásra.

    Példák:
    Statikus: C, Java, C#;
    Dinamikus: Python, JavaScript, Ruby.

  • Erős / gyenge gépelés (néha erősnek / lazának is nevezik). Az erős gépelést az jellemzi, hogy a nyelv nem teszi lehetővé a különböző típusok keverését a kifejezésekben, és nem hajt végre automatikus implicit konverziót, például nem lehet kivonni egy halmazt egy karakterláncból. A gyengén gépelt nyelvek számos implicit konverziót automatikusan végrehajtanak, még akkor is, ha a pontosság elvesztése vagy az átalakítás kétértelmű lehet.

    Példák:
    Erős: Java, Python, Haskell, Lisp;
    Gyenge: C, JavaScript, Visual Basic PHP.

  • Explicit / implicit gépelés. Az explicit módon beírt nyelvek abban különböznek egymástól, hogy az új változók / függvények / argumentumaik típusát kifejezetten be kell állítani. Ennek megfelelően az implicit gépelést használó nyelvek ezt a feladatot a fordítóra/tolmácsra helyezik át.

    Példák:
    Explicit: C++, D, C#
    Implicit: PHP, Lua, JavaScript

Azt is meg kell jegyezni, hogy ezek a kategóriák metszik egymást, például a C nyelv statikus gyenge explicit gépelést, a Python nyelv dinamikus erős implicit gépelést tartalmaz.

Ennek ellenére nincsenek olyan nyelvek, amelyek egyszerre statikus és dinamikus gépeléssel rendelkeznek. Bár előre tekintve, azt mondom, hogy itt fekszem - valóban léteznek, de erről később.

részletes változat

Ha a rövid verzió nem elég neked, rendben. Nem csoda, hogy részletesen írtam? A lényeg az, hogy a rövid verzióba egyszerűen lehetetlen volt minden hasznos és érdekes információ beleférni, a részletes pedig valószínűleg túl hosszú lenne ahhoz, hogy mindenki erőlködés nélkül elolvassa.

Gépelés nélküli gépelés

A nem típusos programozási nyelvekben minden entitást csak különböző hosszúságú bitsorozatnak tekintenek.

A gépelés nélküli gépelés általában az alacsony szintű (assembler nyelv, Forth) és ezoterikus (Brainfuck, HQ9, Piet) nyelvek velejárója. A hátrányai mellett azonban van néhány előnye is.

Előnyök
  • Lehetővé teszi, hogy rendkívül alacsony szinten írjon, és a fordító/tolmács nem zavarja a típusellenőrzéseket. Bármilyen adaton bármilyen műveletet elvégezhet.
  • Az így kapott kód általában hatékonyabb.
  • Az utasítások átláthatósága. A nyelv ismeretében általában nem kétséges, hogy mi ez vagy az a kód.
Hibák
  • Bonyolultság. Gyakran szükség van összetett értékek, például listák, karakterláncok vagy struktúrák megjelenítésére. Ez kellemetlenséget okozhat.
  • Nincs ellenőrzés. Minden értelmetlen művelet, például egy tömbre mutató mutató kivonása egy karakterből, teljesen normálisnak minősül, ami tele van finom hibákkal.
  • Alacsony absztrakciós szint. Bármilyen összetett adattípussal végzett munka nem különbözik a számokkal való munkavégzéstől, ami természetesen sok nehézséget fog okozni.
Erős típus nélküli gépelés?

Igen, ez létezik. Pl. assembly nyelvben (x86 / x86-64 architektúrához, másokat nem ismerek) nem tudsz programot összeállítani, ha a rax regiszterből próbálsz adatokat betölteni (64 bit) a cx regiszterbe (16 bitek).

mov cx, eax ; összeszerelési idő hiba

Szóval kiderült, hogy az assemblernek még van gépelése? Szerintem ezek az ellenőrzések nem elegendőek. És a véleményed természetesen csak tőled függ.

Statikus és dinamikus gépelés

A statikus (statikus) gépelést az különbözteti meg a dinamikustól (dinamikustól), hogy minden típusellenőrzés fordítási időben történik, és nem futási időben.

Egyesek azt gondolhatják, hogy a statikus gépelés túlságosan korlátozó (sőt, az is, de bizonyos technikák segítségével már régen megszabadultak tőle). Egyesek számára a dinamikusan gépelt nyelvek a tűzzel játszanak, de milyen jellemzőkkel tűnnek ki? Van esélye mindkét fajnak létezni? Ha nem, miért van olyan sok statikusan és dinamikusan tipizált nyelv?

Találjuk ki.

A statikus gépelés előnyei
  • A típusellenőrzés csak egyszer, fordítási időben történik. És ez azt jelenti, hogy nem kell folyamatosan kiderítenünk, hogy megpróbálunk-e egy számot karakterlánccal osztani (és hibát dobunk, vagy konverziót hajtunk végre).
  • Végrehajtási sebesség. Az előző pontból kitűnik, hogy a statikusan gépelt nyelvek szinte mindig gyorsabbak, mint a dinamikusan gépelt nyelvek.
  • Bizonyos további feltételek mellett lehetővé teszi a lehetséges hibák észlelését már a fordítási szakaszban.
A dinamikus gépelés előnyei
  • Univerzális gyűjtemények létrehozásának egyszerűsége - rengeteg minden és minden (ritkán merül fel ilyen igény, de amikor felmerül, a dinamikus gépelés segít).
  • Az általánosított algoritmusok leírásának kényelme (például tömbrendezés, amely nemcsak egész számok listáján működik, hanem valós számok listáján is, sőt karakterláncok listáján is).
  • Könnyen megtanulható – A dinamikusan gépelt nyelvek általában nagyon jók a programozás megkezdéséhez.

Általános programozás

Nos, a dinamikus tipizálás melletti legfontosabb érv az általános algoritmusok leírásának kényelmessége. Képzeljünk el egy problémát – több tömbhöz (vagy listához) kell egy keresőfunkció – egy egész számok tömbje, egy valós tömb és egy karaktertömb.

Hogyan fogjuk megoldani? Oldjuk meg 3 különböző nyelven: egy dinamikus gépeléssel, kettő pedig statikus gépeléssel.

Az egyik legegyszerűbb keresési algoritmust fogom használni - a felsorolást. A függvény megkapja a keresett elemet, magát a tömböt (vagy listát), és visszaadja az elem indexét, vagy ha az elem nem található - (-1).

Dinamikus megoldás (Python):

Def find(szükséges_elem, lista): for (index, elem) in enumerate(list): if elem == kötelező_elem: return index return (-1)

Mint látható, minden egyszerű, és nincs probléma azzal, hogy a lista tartalmazhat páros számokat, páros listákat, pedig nincs más tömb. Nagyon jó. Menjünk tovább – oldjuk meg ugyanezt a feladatot C-ben!

Statikus oldat (C):

unsigned int find_int(int kötelező_elem, int tömb, unsigned int size) ( for (unsigned int i = 0; i< size; ++i) if (required_element == array[i]) return i; return (-1); } unsigned int find_float(float required_element, float array, unsigned int size) { for (unsigned int i = 0; i < size; ++i) if (required_element == array[i]) return i; return (-1); } unsigned int find_char(char required_element, char array, unsigned int size) { for (unsigned int i = 0; i < size; ++i) if (required_element == array[i]) return i; return (-1); }

Nos, mindegyik függvény külön-külön hasonlít a Python verzióhoz, de miért van három? Elveszett a statikus programozás?

Igen és nem. Számos programozási technika létezik, ezek közül az egyiket most megvizsgáljuk. Általános programozásnak hívják, és a C++ nyelv elég jól támogatja. Nézzük az új verziót:

Statikus megoldás (általános programozás, C++):

Sablon unsigned int find(T kötelező_elem, std::vektor tömb) ( for (előjel nélküli int i = 0; i< array.size(); ++i) if (required_element == array[i]) return i; return (-1); }

Bírság! Nem tűnik sokkal bonyolultabbnak, mint a Python verzió, és nem kellett sok írás. Ráadásul minden tömbhöz megkaptuk a megvalósítást, nem csak a probléma megoldásához szükséges 3-ra!

Úgy tűnik, ez a verzió pont az, amire szükségünk van – a statikus gépelés és a dinamikus néhány előnye is megvan.

Nagyon jó, hogy ez lehetséges, de lehetne még jobb is. Először is, az általános programozás kényelmesebb és szebb lehet (például a Haskellben). Másodszor, az általános programozáson kívül polimorfizmust (rosszabb lesz az eredmény), funkció túlterhelést (hasonlóan) vagy makrókat is használhat.

Statikus a dinamikában

Azt is meg kell említeni, hogy sok statikus nyelv lehetővé teszi a dinamikus gépelést, például:

  • A C# támogatja a dinamikus pszeudotípust.
  • Az F# támogatja a szintaktikai cukrot a ? operátor formájában, amely a dinamikus gépelés utánzására használható.
  • Haskell – a dinamikus gépelést a Data.Dynamic modul biztosítja.
  • Delphi - egy speciális típusú Varianton keresztül.

Ezenkívül néhány dinamikusan gépelt nyelv lehetővé teszi a statikus gépelés előnyeinek kihasználását:

  • Common Lisp - típusú deklarációk.
  • Perl - az 5.6-os verzió óta, meglehetősen korlátozott.

Erős és gyenge gépelés

Az erősen begépelt nyelvek nem teszik lehetővé a különböző típusú entitások keverését a kifejezésekben, és nem hajtanak végre automatikus konverziót. Ezeket „erősen tipizált nyelveknek” is nevezik. Az angol kifejezés erre az erős tipizálás.

A gyengén tipizált nyelvek éppen ellenkezőleg, arra ösztönzik a programozót, hogy egy kifejezésben keverje össze a különböző típusokat, és maga a fordítóprogram mindent egyetlen típusba konvertál. Ezeket „gyengén tipizált nyelveknek” is nevezik. Az angol kifejezés erre gyenge tipizálás.

A gyenge gépelést gyakran összekeverik a dinamikus gépeléssel, ami teljesen rossz. A dinamikusan tipizált nyelv lehet gyengén és erősen is tipizált.

A gépelés szigorúságának azonban kevesen tulajdonítanak jelentőséget. Gyakran állítják, hogy ha egy nyelvet statikusan írnak be, akkor sok lehetséges fordítási hibát észlelhet. Hazudnak neked!

A nyelvnek erős gépelésnek is kell lennie. Valóban, ha a fordító ahelyett, hogy hibát jelentene, egyszerűen hozzáad egy karakterláncot egy számhoz, vagy ami még rosszabb, kivon egy másikat az egyik tömbből, akkor mi hasznunkra van abból, hogy minden típus "ellenőrzése" a fordítási szakaszban lesz? Így van – a gyenge statikus gépelés még rosszabb, mint az erős dinamikus gépelés! (Hát ez az én véleményem)

Akkor miért nincs semmi előnye a gyenge gépelésnek? Lehet, hogy így tűnik, de annak ellenére, hogy erősen támogatom az erős gépelést, egyet kell értenem, hogy a gyenge gépelésnek is vannak előnyei.

Szeretnéd tudni, hogy melyek?

Az erős gépelés előnyei
  • Megbízhatóság – A helytelen viselkedés helyett kivételt vagy fordítási hibát kap.
  • Sebesség - az implicit konverziók helyett, amelyek meglehetősen drágák lehetnek, erős gépeléssel, explicit módon kell megírni őket, ami legalábbis tudatában van a programozónak, hogy ez a kódrészlet lassú lehet.
  • A program működésének megértése - ismételten az implicit típusú öntvény helyett a programozó mindent maga ír, ami azt jelenti, hogy hozzávetőlegesen megérti, hogy egy karakterlánc és egy szám összehasonlítása nem magától és nem varázslatból történik.
  • Bizonyosság – amikor az átalakításokat kézzel írod, pontosan tudod, hogy mit és mivé alakítasz át. Azt is mindig megérti, hogy az ilyen átalakítások a pontosság elvesztéséhez és hibás eredményekhez vezethetnek.
A gyenge gépelés előnyei
  • A vegyes kifejezések használatának kényelme (például egész és valós számokból).
  • Elvonás a gépeléstől és a feladatra való összpontosítás.
  • A rekord rövidsége.

Oké, kitaláltuk, kiderült, hogy a gyenge gépelésnek is vannak előnyei! Vannak-e módok a gyenge gépelés előnyeinek az erős gépelésre való átültetésére?

Kiderült, hogy még kettő is van.

Implicit típusú öntvény, egyértelmű helyzetekben és adatvesztés nélkül

Hú… Elég hosszú bekezdés. Hadd rövidítsem tovább "korlátozott implicit konverzióra" Mit jelent tehát az egyértelmű helyzet és adatvesztés?

Az egyértelmű helyzet olyan átalakulás vagy művelet, amelyben a lényeg azonnal világos. Például két szám összeadása egyértelmű helyzet. De egy szám tömbbé konvertálása nem (talán egy elemből álló tömb jön létre, esetleg egy ilyen hosszúságú tömb, amely alapértelmezés szerint tele van elemekkel, és lehet, hogy egy számot stringgé alakítanak át, majd tömbbé karakterek).

Az adatvesztés még egyszerűbb. Ha a 3,5 valós számot egész számmá alakítjuk, az adatok egy részét elveszítjük (sőt, ez a művelet is félreérthető - hogyan történik a kerekítés? Felfelé? Lefelé? A tört rész eldobása?).

A kétértelmű helyzetekben történő konverziók és az adatvesztéssel járó konverziók nagyon-nagyon rosszak. Nincs ennél rosszabb a programozásban.

Ha nem hiszi, tanulja meg a PL/I nyelvet, vagy akár csak nézze meg a specifikációját. Konverziós szabályai vannak MINDEN adattípus között! Ez csak a pokol!

Oké, emlékezzünk a korlátozott implicit konverzióra. Vannak ilyen nyelvek? Igen, például Pascalban egy egész számot lehet valós számmá alakítani, de fordítva nem. Hasonló mechanizmusok vannak a C#-ban, a Groovy-ban és a Common Lisp-ben is.

Oké, azt mondtam, hogy van egy másik módja annak, hogy kihasználjuk a gyenge gépelés előnyeit egy erős nyelven. És igen, létezik, és konstruktor polimorfizmusnak hívják.

Elmagyarázom a Haskell csodálatos nyelvezet segítségével.

A polimorf konstruktorok annak a megfigyelésnek az eredményeként jöttek létre, hogy numerikus literálok használatakor leggyakrabban biztonságos implicit konverziókra van szükség.

Például a pi + 1 kifejezésben nem akarsz pi + 1.0 vagy pi + float(1) kifejezést írni. Csak pi + 1-et akarok írni!

És ez a Haskellben történik, köszönhetően annak, hogy a literal 1-nek nincs konkrét típusa. Se nem egész, se nem valós, se nem összetett. Ez csak egy szám!

Ennek eredményeként egy egyszerű x y függvény felírásakor, amely az összes számot x-től y-ig megszorozza (1-es növekedéssel), egyszerre több változatot kapunk - egész számok összege, valós számok összege, racionálisak összege, komplexek összege számok, sőt összegek az összes olyan számtípushoz, amelyeket Ön definiált.

Természetesen ez a technika csak akkor takarít meg, ha vegyes kifejezéseket használ numerikus literálokkal, és ez csak a jéghegy csúcsa.

Így azt mondhatjuk, hogy a legjobb kiút a határon, az erős és gyenge gépelés között egyensúlyozás lesz. Egyelőre azonban egyetlen nyelvnek sincs tökéletes egyensúlya, ezért inkább az erősen gépelt nyelvek felé hajlok (például Haskell, Java, C#, Python), mintsem a gyengén tipizált nyelvekre (például C, JavaScript, Lua, PHP) .

Explicit és implicit gépelés

Egy kifejezetten beírt nyelv megköveteli a programozótól, hogy meghatározza az összes általa deklarált változó és függvény típusát. Ennek angol kifejezése az explicit typing.

Az implicit módon gépelt nyelv viszont arra hív, hogy felejtsük el a típusokat, és bízzuk a típuskövetkeztetést a fordítóra vagy az értelmezőre. Ennek angol kifejezése implicit typing.

Először azt gondolhatnánk, hogy az implicit gépelés egyenértékű a dinamikus gépeléssel, az explicit gépelés pedig a statikus gépeléssel, de később látni fogjuk, hogy ez nem így van.

Vannak-e előnyei mindegyik típusnak, és még egyszer, vannak-e ezek kombinációi, és vannak-e olyan nyelvek, amelyek mindkét módszert támogatják?

Az explicit gépelés előnyei
  • Az egyes függvények aláírása (például int add(int, int)) megkönnyíti a függvény működésének meghatározását.
  • A programozó azonnal felírja, hogy egy adott változóban milyen típusú értékeket lehet tárolni, ami szükségtelenné teszi ennek emlékezését.
Az implicit gépelés előnyei
  • A gyorsírás - def add(x, y) egyértelműen rövidebb, mint az int add(int x, int y) .
  • Rugalmasság a változáshoz. Például, ha egy függvényben az ideiglenes változó ugyanolyan típusú volt, mint a bemeneti argumentum, akkor egy kifejezetten tipizált nyelvben, amikor a bemeneti argumentum típusa megváltozik, az ideiglenes változó típusát is módosítani kell.

Nos, úgy tűnik, mindkét megközelítésnek megvannak az előnyei és hátrányai (és ki várt mást?), ezért keressük a módokat a két megközelítés kombinálására!

Explicit gépelés választás szerint

Vannak olyan nyelvek, amelyek alapértelmezés szerint implicit gépeléssel rendelkeznek, és szükség esetén megadhatják az értékek típusát. A fordító automatikusan következtet a kifejezés valódi típusára. Az egyik ilyen nyelv a Haskell, hadd mondjak egy egyszerű példát a szemléltetésül:

Nincs kifejezett típus add (x, y) = x + y -- Explicit típus add:: (Integer, Integer) -> Integer add (x, y) = x + y

Megjegyzés: Szándékosan használtam egy uncurried függvényt, és szándékosan írtam egy privát aláírást az általánosabb add helyett: (Num a) -> a -> a -> a , mert Meg akartam mutatni az ötletet, anélkül, hogy elmagyaráztam volna Haskell szintaxisát.

Hm. Amint látjuk, nagyon szép és rövid. A függvénybejegyzés soronként csak 18 karaktert tartalmaz, szóközökkel együtt!

Az automatikus típuskövetkeztetés azonban meglehetősen trükkös, és még egy olyan klassz nyelven is, mint a Haskell, néha meghiúsul. (példa erre a monomorfizmus megszorítása)

Léteznek olyan nyelvek, amelyeken alapértelmezés szerint az explicit gépelés, és szükségszerűen az implicit gépelés? Kon
biztosan.

Választott implicit gépelés

Bevezetésre került az új C++ nyelvi szabvány, a C++11 (korábbi nevén C++0x). kulcsszó auto , aminek köszönhetően kényszerítheti a fordítót, hogy a kontextus alapján következtessen a típusra:

Hasonlítsuk össze: // Kézi típusú specifikáció unsigned int a = 5; előjel nélküli int b = a + 3; // Előjel nélküli automatikus típuskövetkeztetés int a = 5; auto b = a + 3;

Nem rossz. De a rekord nem zsugorodott sokat. Lássunk egy példát az iterátorokkal (ha nem érted, ne félj, a legfontosabb, hogy megjegyezd, hogy az automatikus kimenet miatt a rekord jelentősen csökken):

// Kézi típusú std::vector vec = véletlenvektor(30); for (std::vector::const_iterator it = vec.cbegin(); ...) ( ... ) // Automatikus típuskövetkeztetés auto vec = randomVector (harminc); for (auto it = vec.cbegin(); ...) ( ... )

Azta! Itt van a rövidítés. Oké, de lehet tenni valamit Haskell szellemében, ahol a visszatérési típus az argumentumok típusától függ?

A válasz ismét igen, köszönhetően a decltype kulcsszónak és az auto kombinációnak:

// Kézi típusú int divide(int x, int y) ( ... ) // Automatikus típusú levonás auto divide(int x, int y) -> decltype(x / y) ( ... )

Lehet, hogy ez a jelölési forma nem hangzik jól, de az általános kifejezésekkel (sablonokkal / generikusokkal) kombinálva az implicit gépelés vagy az automatikus típuskövetkeztetés csodákra képes.

Néhány programozási nyelv ennek az osztályozásnak megfelelően

Adok egy rövid listát a népszerű nyelvekről, és leírom, hogyan vannak besorolva az egyes „gépelési” kategóriákba.

JavaScript - Dinamikus / Gyenge / Implicit Ruby - Dinamikus / Erős / Implicit Python - Dinamikus / Erős / Implicit Java - Statikus / Erős / Explicit PHP - Dinamikus / Gyenge / Implicit C - Statikus / Gyenge / Explicit C++ - Statikus / Félig erős / Explicit Perl - Dinamikus / Gyenge / Implicit Objective-C - Statikus / Gyenge / Explicit C# - Statikus / Erős / Explicit Haskell - Statikus / Erős / Implicit Common Lisp - Dinamikus / Erős / Implicit

Lehet, hogy valahol hibáztam, főleg a CL-nél, a PHP-nél és az Obj-C-nél, ha más a véleményed valamelyik nyelvről, írd meg kommentben.

Az OO megközelítésben a gépelés egyszerűsége az objektumszámítási modell egyszerűségének a következménye. A részletek kihagyásával azt mondhatjuk, hogy egy OO rendszer végrehajtása során csak egyfajta esemény történik - egy szolgáltatáshívás:


a műveletet jelölve f a hozzá csatolt tárgy felett x, érvelés mellett arg(esetleg több érv, vagy egy sem). A Smalltalk programozók ebben az esetben egy objektum átadásáról beszélnek xüzenetek férvvel arg", de ez csak terminológiai különbség, ezért nem jelentős.

Az, hogy minden ezen az alapkonstrukción alapul, megmagyarázza az OO-elképzelések szépségérzékének egy részét.

Az alapkonstrukcióból következzenek azok a rendellenes helyzetek, amelyek a végrehajtás során előfordulhatnak:

Definíció: típussértés

Futásidejű típussértés vagy röviden csak típussértés a hívás idején történik. x.f(arg), Ahol x tárgyhoz rögzítve OBJ ha akármelyik:

[x]. nincs megfelelő komponens fés alkalmazható OBJ,

[x]. van azonban olyan összetevő, az érvelés arg számára elfogadhatatlan.

A gépelési probléma az ehhez hasonló helyzetek elkerülése:

Az OO rendszerek gépelési problémája

Mikor tapasztaljuk, hogy típussértés fordulhat elő egy OO rendszer végrehajtása során?

A kulcsszó az Amikor. Előbb-utóbb rájön, hogy típussértésről van szó. Például a "Torpedo Launch" komponens végrehajtása egy "Employee" objektumon nem fog működni, és a végrehajtás sikertelen lesz. Előfordulhat azonban, hogy a hibákat a lehető legkorábban szeretné megtalálni, nem pedig később.

Statikus és dinamikus gépelés

Bár köztes lehetőségek is lehetségesek, itt két fő megközelítést mutatunk be:

[x]. Dinamikus gépelés: várja meg, amíg minden hívás befejeződik, majd hozzon döntést.

[x]. Statikus gépelés: adott szabálykészlet alapján határozza meg a forrásszövegből, hogy lehetséges-e típussértés a végrehajtás során. A rendszer akkor fut le, ha a szabályok garantálják, hogy nincsenek hibák.

Ezek a kifejezések könnyen magyarázhatók: dinamikus gépelésnél a típusellenőrzés a rendszer működése közben (dinamikusan), míg statikus gépelésnél statikusan (végrehajtás előtt) történik a szövegen.

A statikus gépelés automatikus ellenőrzést foglal magában, ami általában a fordító feladata. Ennek eredményeként van egy egyszerű definíciónk:

Definíció: statikusan gépelt nyelv

Egy OO nyelv statikusan begépelt, ha a fordító által ellenőrzött következetes szabályokkal rendelkezik, amelyek biztosítják, hogy a rendszer végrehajtása ne vezessen típussértéshez.

A " kifejezés erős gépelés" ( erős). Ez megfelel a definíció ultimátum jellegének, amely megköveteli a típussértés teljes hiányát. Lehetséges és gyenge (gyenge) statikus gépelési formák, amelyekben a szabályok bizonyos jogsértéseket megszüntetnek anélkül, hogy azokat teljesen megszüntetnék. Ebben az értelemben néhány OO nyelv statikusan gyengén van beírva. Harcolni fogunk a legerősebb gépelésért.

A dinamikusan beírt nyelvekben, más néven nem típusos nyelvekben, nincsenek típusdeklarációk, és futás közben bármilyen érték hozzárendelhető az entitásokhoz. Statikus típusellenőrzés nem lehetséges bennük.

Gépelési szabályok

Az OO jelölésünk statikusan van beírva. Típusszabályait a korábbi előadásokon mutatták be, és három egyszerű követelményre bontakoznak ki.

[x]. Minden entitás vagy funkció deklarálásakor meg kell adni a típusát, pl. acc: ACCOUNT. Minden szubrutinnak 0 vagy több formális argumentuma van, amelyek típusát meg kell adni, például: put(x: G; i: INTEGER).

[x]. Bármilyen feladatban x:= yés minden olyan szubrutinhíváson, amelyben y a formális érv tényleges érve x, forrás típus y kompatibilisnek kell lennie a céltípussal x. A kompatibilitás meghatározása az öröklődésen alapul: B kompatibilis valamivel A, ha a leszármazottja, - kiegészítve az általános paraméterekre vonatkozó szabályokkal (lásd 14. előadás).

[x]. Hívás x.f(arg) ezt követeli meg f a céltípus alaposztály-komponense volt x, És f exportálni kell abba az osztályba, amelyben a hívás megjelenik (lásd 14.3).

Realizmus

Bár a statikusan tipizált nyelv definíciója meglehetősen pontos, ez nem elég - informális kritériumok szükségesek a gépelési szabályok létrehozásához. Vegyünk két szélsőséges esetet.

[x]. Tökéletesen helyes nyelvezet, amelyben minden szintaktikailag helyes rendszer egyben típushelyes is. Típus deklarációs szabályokra nincs szükség. Léteznek ilyen nyelvek (gondoljunk csak az egész számok összeadására és kivonására szolgáló lengyel jelölésre). Sajnos egyetlen igazi univerzális nyelv sem felel meg ennek a kritériumnak.

[x]. Teljesen rossz nyelv, amely könnyen létrehozható bármely létező nyelv levételével és egy gépelési szabály hozzáadásával, amely lehetővé teszi Bármi rendszer hibás. Definíció szerint ez a nyelv típusos: mivel nincsenek a szabályoknak megfelelő rendszerek, egyetlen rendszer sem okoz típussértést.

Azt mondhatjuk, hogy az első típusú nyelvek elfér, De hiábavaló, az utóbbi hasznos lehet, de nem megfelelő.

A gyakorlatban egy olyan típusrendszerre van szükség, amely egyszerre hasznos és hasznos: elég erős ahhoz, hogy kielégítse a számítási igényeket, és elég kényelmes ahhoz, hogy ne kényszerítsen bennünket arra, hogy a gépelési szabályokat kielégítő bonyodalmakra menjünk.

Azt fogjuk mondani, hogy a nyelv reális ha használható és hasznos a gyakorlatban. Ellentétben a statikus gépelés definíciójával, amely határozott választ ad a kérdésre: " Az X statikusan van beírva?", a realizmus meghatározása részben szubjektív.

Ebben az előadásban megbizonyosodunk arról, hogy az általunk javasolt jelölés reális legyen.

Pesszimizmus

A statikus gépelés természeténél fogva "pesszimista" politikához vezet. Egy kísérlet ennek garantálására minden számítás nem vezet kudarchoz, elutasítja számításokat, amelyek hiba nélkül zárulhatnak.

Tekintsünk egy szabályos, nem objektív, Pascal-szerű nyelvet különböző típusokkal IGAZIÉs EGÉSZ SZÁM. Leíráskor n: INTEGER; r: Igazi operátor n:=r szabálysértés miatt elutasításra kerül. Így a fordító az összes következő állítást elutasítja:


Ha hagyjuk futni, látni fogjuk, hogy az [A] mindig működni fog, mivel bármely számrendszerben van a 0.0 valós szám pontos reprezentációja, ami egyértelműen 0 egész számra fordítódik. A [B] szinte biztosan működni fog. A [C] művelet eredménye nem nyilvánvaló (a tört rész kerekítésével vagy elvetésével akarjuk megkapni az eredményt?). [D] elvégzi a munkát, akárcsak a kezelő:


ifn^2< 0 then n:= 3.67 end [E]

amely elérhetetlen feladatot tartalmaz ( n^2 a szám négyzete n). Csere után n^2 tovább n csak egy futássorozat adja meg a helyes eredményt. Feladat n egy nagy valós érték, amely nem ábrázolható egész számként, meghibásodást eredményez.

A gépelt nyelveken mindezeket a példákat (működő, nem működő, néha működő) kíméletlenül a típusleírási szabályok megsértéseként kezelik, és minden fordító elutasítja.

A kérdés nem az mi fogunk pesszimisták vagyunk-e, és valójában mennyi megengedhetjük magunknak, hogy pesszimisták legyünk. Visszatérve a realizmus követelményéhez: ha a típusszabályok annyira pesszimisták, hogy megakadályozzák a számítás egyszerű megírását, akkor elutasítjuk őket. De ha a típusbiztonság elérését a kifejezőerő kis mértékű elvesztésével érjük el, akkor elfogadjuk. Például egy fejlesztői környezetben, amely függvényeket biztosít egy egész rész kerekítésére és kivonására - kerekÉs csonka, operátor n:=r helytelennek tekinthető, joggal, mert arra kényszeríti, hogy a kétértelmű alapértelmezett konverziók használata helyett explicit módon írja le a valós-egész szám konverziót.

Statikus gépelés: hogyan és miért

Bár a statikus gépelés előnyei nyilvánvalóak, jó, ha újra beszélünk róluk.

Előnyök

Az előadás elején felsoroltuk a statikus gépelés alkalmazásának okait az objektumtechnológiában. Ez a megbízhatóság, a könnyebb érthetőség és a hatékonyság.

Megbízhatóság olyan hibák észlelése miatt, amelyek egyébként csak működés közben, és csak bizonyos esetekben jelentkezhetnének. A szabályok közül az első, amely az entitások és a függvények deklarálását kényszeríti ki, redundanciát vezet be a programszövegbe, ami lehetővé teszi a fordító számára, hogy a másik két szabályt felhasználva észlelje az inkonzisztenciákat az entitások, komponensek, ill. kifejezéseket.

A hibák korai felismerése azért is fontos, mert minél tovább késlekedünk a feltárásukkal, annál inkább nő a kijavítás költsége. Ezt a tulajdonságot, amely minden professzionális programozó számára intuitívan érthető, Boehm széles körben ismert művei kvantitatívan megerősítik. A korrekciós költség függését a hibák megtalálásának időpontjától egy grafikon mutatja, amely számos nagy ipari projekt és egy kis ellenőrzött projekttel végzett kísérlet alapján készült:

Rizs. 17.1.Összehasonlító hibajavítási költségek (engedéllyel közzétéve)

Olvashatóság vagy Könnyű megértés(olvashatóság) megvannak az előnyei. A könyvben szereplő összes példában egy típus megjelenése egy entitáson információt ad az olvasónak annak céljáról. Az olvashatóság rendkívül fontos a karbantartási szakaszban.

Végül, hatékonyság meghatározhatja az objektumtechnológia sikerességét vagy kudarcát a gyakorlatban. Statikus gépelés hiányában a végrehajtás során x.f(arg) bármennyi időbe telhet. Ennek az az oka, hogy futás közben nem talál f alap célosztályban x, leszármazottaiban folytatódik a keresés, és ez biztos út az eredménytelenség felé. Enyhítheti a problémát, ha javítja egy összetevő keresését a hierarchiában. Az Self nyelv szerzői sokat dolgoztak azon, hogy a legjobb kódot generálják egy dinamikusan tipizált nyelvhez. De a statikus gépelés tette lehetővé, hogy egy ilyen OO termék hatékonysága megközelítse a hagyományos szoftvereket, vagy azzal megegyezzen.

A statikus gépelés kulcsa az a már megfogalmazott gondolat, hogy a fordító, amely a konstrukció kódját generálja x.f(arg), ismeri a típust x. A polimorfizmus miatt nem lehet egyedileg meghatározni a komponens megfelelő változatát f. De a deklaráció leszűkíti a lehetséges típusok körét, lehetővé téve a fordító számára, hogy olyan táblát hozzon létre, amely hozzáférést biztosít a megfelelő f minimális költséggel, korlátozott állandóval hozzáférési nehézség. További optimalizálás végrehajtva statikus kötésÉs cserék (inlining)- a statikus gépelés is megkönnyíti, teljesen kiküszöbölve az általános költségeket, ahol lehetséges.

Érvek a dinamikus gépelés mellett

Mindezek ellenére a dinamikus gépelés nem veszíti el híveit, különösen a Smalltalk programozók körében. Érveléseik elsősorban a fentebb tárgyalt realizmuson alapulnak. Úgy vélik, hogy a statikus gépelés túlságosan korlátozó, megakadályozza őket kreatív ötleteik szabad kifejezésében, néha "szüzességövnek" nevezik.

Ezzel az érveléssel egyetérthetünk, de csak a statikusan beírt nyelvek esetében, amelyek nem támogatnak számos funkciót. Érdemes megjegyezni, hogy a típus fogalmához kapcsolódó és a korábbi előadásokon bemutatott összes fogalom szükséges - ezek bármelyikének elutasítása komoly megszorításokkal jár, bevezetésük pedig éppen ellenkezőleg, rugalmasságot ad cselekvéseinknek, lehetőséget kínálunk arra, hogy teljes mértékben élvezzük a statikus gépelés gyakorlatiasságát.

Tipizálás: a siker összetevői

Melyek a valósághű statikus gépelés mechanizmusai? Mindegyiket bemutattuk az előző előadásokon, ezért csak röviden felidéznünk kell őket. Közös felsorolásuk mutatja társulásuk koherenciáját és erejét.

Típusrendszerünk teljes mértékben a koncepcióra épül osztály. Az osztályok még olyan alaptípusok is, mint EGÉSZ SZÁM, ezért nincs szükségünk speciális szabályokra az előre meghatározott típusok leírásához. (Itt tér el a jelölésünk a "hibrid" nyelvektől, mint például az Object Pascal, a Java és a C++, ahol a régi nyelvek típusrendszerét osztályalapú objektumtechnológiával kombinálják.)

Kibővített típusok nagyobb rugalmasságot biztosít számunkra azáltal, hogy engedélyezi azokat a típusokat, amelyek értékei objektumokat jelölnek, valamint olyan típusokat, amelyek értékei hivatkozásokat jelölnek.

A rugalmas típusrendszer kialakításában a döntő szóhoz tartozik öröklésés a kapcsolódó fogalom kompatibilitás. Ezzel áthidaljuk a klasszikus gépelt nyelvek, például a Pascal és az Ada fő korlátait, amelyekben az operátor x:= y megköveteli, hogy a típus xÉs y ugyanaz volt. Ez a szabály túl szigorú: tiltja olyan entitások használatát, amelyek rokon típusú objektumokat jelölhetnek ( TAKARÉKSZÁMLAÉs FIÓK ELLENŐRZÉSE). Az öröklődésnél csak típuskompatibilitást kérünk y típussal x, Például, x típusa van FIÓK, y- TAKARÉKSZÁMLA, a második osztály pedig az első utódja.

A gyakorlatban egy statikusan tipizált nyelvnek támogatásra van szüksége többszörös öröklődés. A statikus gépelés ismert alapvető vádjai, hogy nem ad lehetőséget az objektumok különböző módon történő értelmezésére. Igen, a tárgy DOKUMENTUM(dokumentum) továbbítható a hálózaton keresztül, ezért szükséges a típushoz társított komponensek jelenléte ÜZENET(üzenet). De ez a kritika csak azokra a nyelvekre igaz, amelyek egyedi öröklődésre korlátozódnak.

Rizs. 17.2. Többszörös öröklés

Sokoldalúság szükséges például rugalmas, de biztonságos konténer adatszerkezetek leírásához (pl osztály LISTA[G]...). E mechanizmus nélkül a statikus gépeléshez különböző osztályok deklarálására lenne szükség a különböző elemtípusú listákhoz.

Bizonyos esetekben sokoldalúságra van szükség korlátoz, amely lehetővé teszi olyan műveletek használatát, amelyek csak általános típusú entitásokra vonatkoznak. Ha az általános osztály SORTABLE_LIST támogatja a rendezést, ehhez típusú entitások szükségesek G, Ahol G- általános paraméter, összehasonlító művelet jelenléte. Ezt a linkkel érjük el G az általános megszorítást meghatározó osztály - HASONLÓ:


osztály SORTABLE_LIST...

Bármely tényleges általános paraméter SORTABLE_LIST osztály gyermekének kell lennie HASONLÓ, amely tartalmazza a szükséges komponenst.

Egy másik lényeges mechanizmus az hozzárendelési kísérlet- megszervezi a hozzáférést azokhoz az objektumokhoz, amelyek típusát a szoftver nem szabályozza. Ha y egy adatbázis-objektum vagy egy hálózaton keresztül kapott objektum, akkor az utasítás x ?= y hozzárendelni x jelentése y, Ha y kompatibilis típusú, vagy ha nem, akkor megadja x jelentése Üres.

Nyilatkozatok, amelyek a Design by Contract ötlet részeként osztályokhoz és összetevőikhez vannak társítva előfeltételek, utófeltételek és osztályinvariánsok formájában, lehetővé teszik olyan szemantikai megszorítások leírását, amelyekre nem vonatkozik típusspecifikáció. Az olyan nyelveknek, mint a Pascal és az Ada, vannak olyan tartománytípusok, amelyek például 10 és 20 közé korlátozhatják egy entitás értékét, de nem használhatja őket az érték kényszerítésére. én negatív volt, mindig kétszerese j. Az osztályinvariánsok a segítségükre vannak, hogy pontosan tükrözzék a bevezetett korlátozásokat, bármilyen bonyolultak is legyenek.

Rögzített hirdetések szükségesek a gyakorlatban a kódduplikáció elkerülése érdekében. hirdetve y: mint x, erre garanciát kapsz y minden ismételt típusdeklaráció után megváltozik x egy leszármazottnál. Ennek a mechanizmusnak a hiányában a fejlesztők szüntelenül újra nyilatkoznának, és igyekeznének konzisztensen tartani a különböző típusokat.

A ragadós deklarációk az utolsó nyelvi mechanizmus speciális esetei, amelyekre szükségünk van - kovariancia, amelyről később részletesebben lesz szó.

A szoftverrendszerek fejlesztése során valójában még egy olyan tulajdonságra van szükség, amely magában a fejlesztői környezetben rejlik - gyors növekményes újrafordítás. Amikor ír vagy módosít egy rendszert, a lehető leghamarabb látni szeretné a változtatások hatását. Statikus gépeléssel időt kell adni a fordítónak a típusok újraellenőrzésére. A hagyományos fordítási rutinok megkövetelik a teljes rendszer újrafordítását (és összeállítása), és ez a folyamat fájdalmasan hosszú lehet, különösen a nagyméretű rendszerekre való átálláskor. Ez a jelenség amellett szóló érv lett tolmácsolása rendszerek, mint például a korai Lisp vagy Smalltalk környezetek, amelyek a rendszert kis feldolgozás vagy típusellenőrzés nélkül futtatták. Mára ez az érvelés feledésbe merült. Egy jó modern fordító észleli, hogyan változott a kód a legutóbbi fordítás óta, és csak azokat a változtatásokat dolgozza fel, amelyeket talált.

"A baba tipikus"?

A mi célunk - szigorú statikus gépelés. Éppen ezért el kell kerülnünk a kiskapukat a „szabályok szerint való játékunkban”, legalábbis pontosan azonosítanunk kell azokat, ha vannak.

A statikusan beírt nyelvek leggyakoribb hiányossága az olyan konverziók megléte, amelyek megváltoztatják az entitás típusát. A C-ben és származékos nyelveiben ezeket "type casting"-nak vagy öntésnek nevezik. Felvétel (OTHER_TYPE) x jelzi, hogy az érték x a fordító úgy érzékeli, hogy rendelkezik a típussal OTHER_TYPE, a lehetséges típusokra vonatkozó bizonyos korlátozások mellett.

Az ehhez hasonló mechanizmusok megkerülik a típusellenőrzés korlátait. Az öntés gyakori a C programozásban, beleértve az ANSI C nyelvjárást is. Még a C++ nyelvben is a típusöntés, bár kevésbé gyakori, továbbra is gyakori és talán szükséges is.

A statikus gépelés szabályait nem is olyan egyszerű betartani, ha azok öntéssel bármikor megkerülhetők.

Gépelés és linkelés

Bár a könyv olvasójaként biztosan meg fogja különböztetni a statikus gépelést a statikustól kötés Nos, vannak, akik erre nem képesek. Ennek részben a Smalltalk nyelv hatása lehet, amely mindkét probléma dinamikus megközelítését hirdeti, és ahhoz a tévhithez vezethet, hogy ugyanaz a megoldás. (Ebben a könyvben azzal érvelünk, hogy kívánatos a statikus gépelés és a dinamikus linkelés kombinálása robusztus és rugalmas programok létrehozása érdekében.)

Mind a gépelés, mind a kötés az alapkonstrukció szemantikájával foglalkozik x.f(arg) de válaszolj két különböző kérdésre:

Gépelés és linkelés

[x]. Kérdés a gépeléssel kapcsolatban: amikor biztosan tudnunk kell, hogy egy műveletnek megfelelő f Az entitáshoz csatolt objektumra vonatkozik x(paraméterrel arg)?

[x]. Kapcsoló kérdés: Mikor kell tudnunk, hogy egy adott hívás milyen műveletet kezdeményez?

A gépelés választ ad a rendelkezésre állás kérdésére legalább egy műveletek, a kötés felelős a kiválasztásért szükséges.

Az objektum megközelítés keretein belül:

[x]. A gépelési probléma összefügg polimorfizmus: mert a xfutásidőben több különböző típusú objektumot jelölhet, meg kell győződnünk arról, hogy a reprezentáló művelet f, elérhető ezen esetek mindegyikében;

[x]. kötési probléma okozta ismételt bejelentések: mivel egy osztály megváltoztathatja az örökölt komponenseket, előfordulhat, hogy két vagy több művelet is képviseli magát f ebben a felhívásban.

Mindkét probléma megoldható dinamikusan és statikusan is. A meglévő nyelvek mind a négy megoldást kínálják.

[x]. Számos nem objektív nyelv, mint például a Pascal és az Ada, egyaránt megvalósítja a statikus gépelést és a statikus linkelést. Minden entitás csak egy statikusan meghatározott típusú objektumot képvisel. Ez biztosítja a megoldás megbízhatóságát, melynek ára a rugalmassága.

[x]. A Smalltalk és más OO nyelvek dinamikus linkelést és dinamikus gépelést tartalmaznak. Előnyben részesítik a rugalmasságot a nyelv megbízhatóságának rovására.

[x]. Egyes nem objektív nyelvek támogatják a dinamikus gépelést és a statikus linkelést. Köztük van assembly nyelvek és számos szkriptnyelv.

[x]. A statikus gépelés és a dinamikus kötés gondolatait a könyvben javasolt jelölések testesítik meg.

Vegye figyelembe a C ++ nyelv sajátosságait, amely támogatja a statikus gépelést, bár nem szigorú a típusöntvény jelenléte miatt, statikus kötés (alapértelmezés szerint), dinamikus kötés virtuális esetén ( virtuális) hirdetéseket.

A statikus gépelés és a dinamikus kötés választásának oka nyilvánvaló. Az első kérdés: "Mikor fogunk tudni az összetevők létezéséről?" - statikus választ javasol: " Minél korábban, annál jobb", ami azt jelenti: fordítási időben. A második kérdés: "Melyik komponenst használja?" dinamikus választ javasol: " akire szüksége van", amely megfelel az objektum futási időben meghatározott dinamikus típusának. Ez az egyetlen elfogadható megoldás, ha a statikus és a dinamikus kötés eltérő eredményt ad.

Az öröklési hierarchia következő példája segít tisztázni ezeket a fogalmakat:

Rizs. 17.3. Repülőgép típusok

Fontolja meg a hívást:


my_aircraft.lower_landing_gear

Egy kérdés a gépeléssel kapcsolatban: mikor kell megbizonyosodni arról, hogy egy komponens itt lesz alsó_futómű("kioldó futómű"), egy tárgyra vonatkozik (az COPTER egyáltalán nem lesz) A kötés kérdése: a több lehetséges változat közül melyiket válasszuk.

A statikus kötés azt jelentené, hogy figyelmen kívül hagyjuk a csatolt objektum típusát, és az entitásdeklarációra hagyatkozunk. Ebből kifolyólag, amikor egy Boeing 747-400-assal foglalkozunk, a hagyományos 747-es sorozatú utasszállítókhoz tervezett változatot kívánjuk, és nem a 747-400-as módosításukat. A dinamikus kötés alkalmazza az objektum által igényelt műveletet, és ez a helyes megközelítés.

Statikus gépeléssel a fordító nem utasít el egy hívást, ha garantálható, hogy a program végrehajtásakor az entitáshoz én_repülőgépem hozzá kell csatolni a megfelelő alkatrészhez mellékelt tárgyat alsó_futómű. A garancia megszerzésének alaptechnikája egyszerű: kötelező nyilatkozattal én_repülőgépem típusának alaposztálya köteles tartalmazni egy ilyen komponenst. Ezért én_repülőgépem nem deklarálható REPÜLŐGÉP, mivel az utóbbinak nincs alsó_futómű ezen a szinten; a helikopterek, legalábbis a mi példánkban, nem tudják, hogyan kell kiengedni a futóművet. Ha egy entitást mint REPÜLŐGÉP, - a szükséges komponenst tartalmazó osztály - minden rendben lesz.

A Smalltalk stílusban történő dinamikus gépelés megköveteli, hogy várjon a hívásra, és annak végrehajtása során ellenőrizze a kívánt összetevő jelenlétét. Ez a viselkedés prototípusoknál és kísérleti fejlesztéseknél lehetséges, de ipari rendszerek esetében elfogadhatatlan – repülés közben már késő megkérdezni, hogy van-e futóműve.

Kovariancia és gyermekrejtőzés

Ha a világ egyszerű lenne, akkor a gépelésről szóló beszélgetésnek vége lehetne. Meghatároztuk a statikus tipizálás céljait és előnyeit, megvizsgáltuk azokat a korlátokat, amelyeknek a reális típusrendszereknek meg kell felelniük, és ellenőriztük, hogy a javasolt tipizálási módszerek megfelelnek-e a kritériumainknak.

De a világ nem egyszerű. A statikus gépelés egyes szoftverfejlesztési követelményekkel kombinálva összetettebb problémákat okoz, mint amilyennek látszik. A problémákat két mechanizmus okozza: kovariancia- paramétertípusok megváltoztatása újradefiniáláskor, leszármazott bujkál- egy leszármazott osztály azon képessége, hogy korlátozza az örökölt komponensek exportállapotát.

kovariancia

Mi történik egy komponens argumentumaival, ha a típusát újradefiniálják? Ez komoly probléma, és számos példát láttunk már rá: eszközök és nyomtatók, egyszeresen és kétszeresen összekapcsolt listák és így tovább (lásd a 16.6., 16.7. szakaszokat).

Íme egy másik példa, amely segít tisztázni a probléma természetét. És bár távol áll a valóságtól és metaforikus, a programsémákhoz való közelsége nyilvánvaló. Emellett az elemzés során gyakran visszatérünk a gyakorlatból származó problémákhoz.

Képzeljünk el egy bajnokságra készülő egyetemi sícsapatot. Osztály LÁNY olyan síelőket foglal magában, akik a női csapat tagjai, FIÚ- síelők. Mindkét csapat számos résztvevője rangsorolt, jó eredményeket felmutatva a korábbi versenyeken. Ez azért fontos számukra, mert most ők futnak majd elsőként, előnyt szerezve a többiekkel szemben. (Ez a szabály, amely az amúgy is kiváltságosokat részesíti előnyben, talán az, ami sokak szemében olyan vonzóvá teszi a szlalomot és a sífutást, amely jó metaforája magának az életnek.) Tehát van két új osztályunk: RANKED_GIRLÉs RANKED_BOY.

Rizs. 17.4. Síelő besorolás

A sportolók elhelyezésére számos szoba foglalt: csak férfiaknak, csak lányoknak, csak női győzteseknek. Ennek megjelenítéséhez párhuzamos osztályhierarchiát használunk: SZOBA, GIRL_ROOMÉs RANKED_GIRL_ROOM.

Íme az osztály vázlata SÍELŐ:


- Szobatárs.
... Egyéb lehetséges összetevők ebben és az azt követő órákban kimaradt...

Két komponens érdekel bennünket: attribútum szobatársés eljárás Ossza meg, amely ezt a síelőt egy helyiségbe "helyezi" az aktuális síelővel:


Egy entitás deklarálásakor Egyéb eldobhatod a típust SÍELŐ a fix típus javára mint szobatárs(vagy mint a Current Mert szobatársÉs Egyéb egyidejűleg). De felejtsük el egy pillanatra a típusrögzítést (ezekre később még visszatérünk), és nézzük meg a kovariancia problémáját eredeti formájában.

Hogyan lehet bevezetni a típusfelülírást? A szabályok külön lakóhelyet írnak elő a fiúknak és a lányoknak, a nyerteseknek és a többi résztvevőnek. Ennek a problémának a megoldására az újradefiniáláskor megváltoztatjuk a komponens típusát szobatárs, az alábbiak szerint (a továbbiakban a felülírt elemek aláhúzottak).


- Szobatárs.

Határozza meg újra az eljárás érvelését Ossza meg. Az osztály teljesebb verziója most így néz ki:


- Szobatárs.
-- Válasszon másikat szomszédnak.

Hasonlóképpen módosítania kell az összes generált forrást SÍELŐ osztályok (most nem használunk típusjavítást). Ennek eredményeként van egy hierarchiánk:

Rizs. 17.5. A tagok hierarchiája és újradefiniálása

Mivel az öröklődés egy specializáció, a típusszabályok megkövetelik, hogy egy komponens eredményének felülbírálásakor ebben az esetben szobatárs, az új típus az eredeti gyermeke volt. Ugyanez vonatkozik az argumentum típusának újradefiniálására is. Egyéb rutinokat Ossza meg. Ezt a stratégiát, mint tudjuk, kovariancia-nak nevezzük, ahol a "ko" előtag a paraméter és az eredmény típusának együttes változását jelzi. Az ellenkező stratégiát ún ellentmondás.

Minden példánk meggyőzően bizonyítja a kovariancia gyakorlati szükségességét.

[x]. Egyedül linkelt listaelem KAPCSOLATOS társítani kell egy másik hasonló elemhez és a példányhoz BI_LINKABLE- hasonlóval. A kovariánst felül kell írni, és az argumentumot be kell írni put_right.

[x]. Minden szubrutin benne van LINKED_LIST típus argumentummal KAPCSOLATOS költözéskor TWO_WAY_LISTérvre lesz szükség BI_LINKABLE.

[x]. Eljárás set_alternate elfogadja ESZKÖZ- érvelés az órán ESZKÖZÉs NYOMTATÓ-érv - az órán NYOMTATÓ.

A kovariáns újradefiniálás különösen népszerű, mivel az információrejtés az űrlap eljárásainak létrehozásához vezet


-- Az attribútum beállítása v.

valakivel együtt dolgozni attrib típus SOME_TYPE. Az ilyen eljárások természetesen kovariánsak, mivel minden olyan osztálynak, amely megváltoztatja az attribútum típusát, ennek megfelelően újra kell definiálnia az argumentumot. set_attrib. Bár a bemutatott példák egy sémába illeszkednek, a kovariancia sokkal elterjedtebb. Gondoljunk például egy olyan eljárásra vagy függvényre, amely egyszeresen összekapcsolt listák összefűzését hajtja végre ( LINKED_LIST). Az argumentumát újra kell definiálni duplán linkelt listaként ( TWO_WAY_LIST). Univerzális kiegészítési művelet infix "+" elfogadja SZÁM- érvelés az órán SZÁM, IGAZI- osztályban IGAZIÉs EGÉSZ SZÁM- osztályban EGÉSZ SZÁM. Párhuzamos telefonszolgáltatási hierarchiákban, eljárás Rajt osztályban PHONE_SERVICEérvre lehet szükség CÍM, amely az előfizető címét jelenti, (számlázáshoz), míg az osztályban ugyanez az eljárás CORPORATE_SERVICE típusú argumentum szükséges CORPORATE_ADDRESS.

Rizs. 17.6. Kommunikációs szolgáltatások

Mit lehet mondani a kontravariáns megoldásról? A síelő példájában ez azt jelentené, hogy ha átmegy az osztályba RANKED_GIRL, eredmény típusa szobatársújradefiniálva mint RANKED_GIRL, akkor az ellentmondás miatt az érv típusa Ossza meg típusra újradefiniálható LÁNY vagy SÍELŐ. Az egyetlen típus, amely nem megengedett a kontravariáns megoldás alatt, az RANKED_GIRL! Elég, hogy a legrosszabb gyanút keltse a lányok szüleiben.

Párhuzamos hierarchiák

Annak érdekében, hogy ne maradjon szó nélkül, fontolja meg a példa egy változatát SÍELŐ két párhuzamos hierarchiával. Ez lehetővé teszi számunkra, hogy szimuláljunk egy olyan helyzetet, amellyel a gyakorlatban már találkoztunk: TWO_WAY_LIST > LINKED_LISTÉs BI_LINKABLE > LINKABLE; vagy hierarchia telefonszolgáltatással PHONE_SERVICE.

Legyen hierarchia az osztállyal SZOBA, akinek leszármazottja GIRL_ROOM(Osztály FIÚ kihagyva):

Rizs. 17.7. Síelők és szobák

A síelő osztályaink ebben a párhuzamos hierarchiában ahelyett szobatársÉs Ossza meg hasonló alkatrészei lesznek szállás (szállás) És befogadni (hely):


leírás: "Új változat párhuzamos hierarchiákkal"
accommodate (r: ROOM) is ... megköveteli ... do

Kovariáns felülírásokra itt is szükség van: az osztályban LÁNY1 Hogyan szállás, és a szubrutin argumentuma befogadni típusra kell cserélni GIRL_ROOM, osztályban FIÚ1- típus BOY_ROOM stb. (Ne feledje, továbbra is típusrögzítés nélkül dolgozunk.) A példa előző verziójához hasonlóan itt is haszontalan az ellentmondás.

A polimorfizmus félrevezetése

Nincs elég példa a kovariancia célszerűségére? Miért gondolna bárki olyan ellentmondást, amely ütközik azzal, ami a gyakorlatban szükséges (néhány fiatal viselkedésén kívül)? Ennek megértéséhez vegyük figyelembe a polimorfizmus és a kovariancia-stratégia kombinálásakor felmerülő problémákat. Nem nehéz kitalálni egy szabotázssémát, és lehet, hogy már maga hozta létre:


létrehoz b; létrehozás g;-- Fiú és LÁNY objektumok létrehozása.

Az utolsó hívás eredménye, ami a fiataloknak igencsak tetszetős volt, pontosan az, amit típusfelülírással próbáltunk megakadályozni. Hívás Ossza meg vezet arra a tényre, hogy az objektum FIÚ, ismert, mint bés a polimorfizmusnak köszönhetően álnevet kapott s típus SÍELŐ, az objektum szomszédjává válik LÁNY néven ismert g. A felhívás azonban, bár a szálló szabályzatával ellentétes, a műsorszövegben egészen korrekt, hiszen Ossza meg-exportált komponens összetételében SÍELŐ, A LÁNY, argumentumtípus g, kompatibilis valamivel SÍELŐ, a formális paraméter típusa Ossza meg.

A párhuzamos hierarchia séma ugyanolyan egyszerű: cserélje ki SÍELŐ tovább SÍLŐ1, kihívás Ossza meg- hívásra s.accommodate(gr), Ahol gr- típusú entitás GIRL_ROOM. Az eredmény ugyanaz.

Ha ezekre a problémákra ellentétes megoldást választunk, akkor nem lenne lehetőség: a híváscél specializációja (példánkban s) az érvelés általánosítását igényelné. Ennek eredményeként az ellentmondás a mechanizmus egyszerűbb matematikai modelljéhez vezet: öröklődés - újradefiníció - polimorfizmus. Ezt a tényt számos elméleti cikk leírja, amelyek ezt a stratégiát javasolják. Az érvelés nem túl meggyőző, mert mint példáink és más publikációk mutatják, az ellentmondásnak nincs gyakorlati haszna.

Ezért anélkül, hogy megpróbálnánk a kovariáns testre húzni az ellentmondásos ruhákat, el kell fogadni a kovariáns valóságot, és keresni kell a nemkívánatos hatás kiküszöbölésének módjait.

Leszármazott által elrejtőzött

Mielőtt megoldást keresne a kovariancia problémájára, fontoljon meg egy másik mechanizmust, amely típussértésekhez vezethet polimorfizmus körülményei között. A leszármazottak elrejtése egy osztály azon képessége, hogy ne exportálja a szüleitől kapott komponenst.

Rizs. 17.8. Leszármazott által elrejtőzött

Tipikus példa az alkatrész add_vertex(csúcs hozzáadása) az osztály által exportált POLIGON, de a leszármazottja rejtette el TÉGLALAP(tekintettel az invariáns esetleges megsértésére - az osztály téglalap akar maradni):


Nem programozási példa: a "Strucc" osztály elrejti a "Madár" szülőtől kapott "Fly" metódust.

Vegyük egy pillanatra ezt a sémát, és kérdezzük meg, hogy az öröklés és a rejtőzködés kombinációja jogos lenne-e. A rejtőzködés modellező szerepét, akárcsak a kovariancia, megsértik a polimorfizmus miatt lehetséges trükkök. És itt nem nehéz olyan rosszindulatú példát építeni, amely lehetővé teszi az összetevő elrejtése ellenére annak meghívását és egy csúcs hozzáadását a téglalaphoz:


alkotó; -- Hozzon létre egy RECTANGLE objektumot.
p:=r; -- Polimorf feladat.

Mivel a tárgy r a lényeg alá bújva p osztály POLIGON, A add_vertex exportált alkatrész POLIGON, akkor az entitás hívja p helyes. A végrehajtás eredményeként egy újabb csúcs jelenik meg a téglalapban, ami azt jelenti, hogy érvénytelen objektum jön létre.

A rendszerek és osztályok helyessége

A kovariancia és a leszármazottak elrejtésének problémáinak megvitatásához új kifejezésekre van szükségünk. Majd hívjuk osztály-helyes (osztály-érvényes) olyan rendszert, amely megfelel az előadás elején adott három típusleírási szabálynak. Emlékezzünk vissza: minden entitásnak megvan a maga típusa; a tényleges érv típusának kompatibilisnek kell lennie a formális érv típusával, hasonló a helyzet a hozzárendeléssel; a hívott komponenst deklarálni kell az osztályában, és exportálni kell a hívást tartalmazó osztályba.

A rendszer ún rendszer-helyes (rendszerérvényes), ha nem történik típussértés a végrehajtása során.

Ideális esetben mindkét fogalomnak meg kell egyeznie. Azt azonban már láttuk, hogy egy osztályhelyes rendszer az öröklődés, a kovariancia és a leszármazott általi elrejtőzés feltételei között nem biztos, hogy rendszerhelyes. Nevezzük ezt a hibát rendszer érvényességi hibája.

Gyakorlati szempont

A probléma egyszerűsége egyfajta paradoxont ​​teremt: egy érdeklődő kezdő percek alatt tud ellenpéldát konstruálni, a valós gyakorlatban a rendszerek osztályhelyességének hibái nap mint nap előfordulnak, de a rendszerhelyesség megsértése még nagy, több helyen is. éves projektek, rendkívül ritkán fordulnak elő.

Ez azonban nem teszi lehetővé, hogy figyelmen kívül hagyjuk őket, ezért elkezdjük tanulmányozni a probléma megoldásának három lehetséges módját.

Ezután a tárgyi megközelítés nagyon finom és nem túl gyakran megnyilvánuló aspektusait fogjuk érinteni. Ha először olvassa ezt a könyvet, érdemes kihagynia az előadás többi részét. Ha még nem ismeri az OO technológiát, akkor jobban megértheti ezt az anyagot, miután elolvasta az "Objektumorientált tervezés alapjai" kurzus 1-11. előadásait, amelyek az öröklődés módszertanával foglalkoznak, és különösen az "Alapismeretek" kurzus 6. előadásában. Objektum-orientált tervezés", amely a módszertan öröklésének szentelt.

A rendszerek helyessége: első közelítés

Koncentráljunk először a kovariancia problémájára, amely a kettő közül a fontosabb. Kiterjedt szakirodalom foglalkozik ezzel a témával, és számos különféle megoldást kínál.

Ellentmondás és változatlanság

Az ellentmondás kiküszöböli a rendszer helyességének megsértésével kapcsolatos elméleti problémákat. Ez azonban elveszíti a típusrendszer realizmusát, ezért nincs szükség ennek a megközelítésnek a továbbgondolására.

A C++ nyelv eredetisége abban rejlik, hogy a stratégiát használja novariancia, megakadályozza, hogy módosítsa az argumentumok típusát a felülírt szubrutinokban! Ha a C++ erősen tipizált nyelv lenne, akkor a típusrendszerét nehéz lenne használni. A probléma legegyszerűbb megoldása ezen a nyelven, valamint a C ++ egyéb korlátainak (mondjuk a korlátozott univerzalitás hiányának) megkerülése a casting - type casting használata, amely lehetővé teszi a meglévő gépelési mechanizmus teljes figyelmen kívül hagyását. Ez a megoldás nem tűnik vonzónak. Megjegyezzük azonban, hogy számos, alább tárgyalt javaslat a varianciamentességre fog támaszkodni, aminek értelmét a kovariáns újradefiniálás helyett a típusokkal való munka új mechanizmusainak bevezetése adja.

Általános paraméterek használata

Az egyetemesség egy érdekes ötlet középpontjában áll, amelyet először Franz Weber javasolt. Deklaráljunk egy osztályt SÍLŐ1, amely az általános paraméterek generálását az osztályra korlátozza SZOBA:


osztályú SKIER1 funkció
accommodate (r: G) is ... igényel ... do szállás:= r end

Aztán az osztály LÁNY1 lesz az örökös SÍLŐ1 stb. Ugyanez a technika, bármilyen furcsának is tűnik első pillantásra, párhuzamos hierarchia hiányában is használható: osztályú SÍLŐ.

Ez a megközelítés megoldja a kovariancia problémáját. Az osztály minden használatának meg kell adnia a tényleges általános paramétert SZOBA vagy GIRL_ROOM, így a rossz kombináció egyszerűen lehetetlenné válik. A nyelv változatlanná válik, és a rendszer teljes mértékben kielégíti a kovariancia igényeit az általános paraméterek miatt.

Sajnos ez a technika általános megoldásként elfogadhatatlan, mivel az általános paraméterek növekvő listájához vezet, minden lehetséges kovariáns argumentumhoz egy-egy. Rosszabb esetben egy kovariáns szubrutin hozzáadása olyan argumentummal, amelynek típusa nem szerepel a listában, egy általános osztályparaméter hozzáadását igényelné, és ezért megváltozna az osztály interfésze, ami az osztály összes kliensének megváltozását okozza, ami elfogadhatatlan.

Típus változók

Számos szerző, köztük Kim Bruce, David Shang és Tony Simons, olyan típusú változókon alapuló megoldással állt elő, amelyek értéke típusok. Az ötletük egyszerű:

[x]. kovariáns felülírások helyett típusváltozókat használó típusdeklarációk engedélyezése;

[x]. a típuskompatibilitási szabályok kiterjesztése az ilyen változók kezelésére;

[x]. lehetővé teszi a típusváltozók értékként történő hozzárendelését a nyelvtípusokhoz.

Az olvasók ezen gondolatok részletes bemutatását számos, a témával foglalkozó cikkben, valamint Cardelli (Cardelli), Castagna (Castagna), Weber (Weber) és mások kiadványaiban találhatják meg. Nem foglalkozunk ezzel a problémával, és itt van, miért.

[x]. A megfelelően megvalósított típusváltozó-mechanizmus abba a kategóriába tartozik, amely lehetővé teszi egy típus használatát anélkül, hogy azt teljes mértékben meghatározná. Ugyanebbe a kategóriába tartozik a sokoldalúság és a hirdetések rögzítése. Ez a mechanizmus helyettesítheti az ebbe a kategóriába tartozó más mechanizmusokat. Ez eleinte a típusváltozók javára is értelmezhető, de az eredmény katasztrofális lehet, hiszen nem világos, hogy ez az átfogó mechanizmus képes-e minden feladatot az általánosságban és a típusrögzítésben rejlő könnyedséggel és egyszerűséggel kezelni.

[x]. Tételezzük fel, hogy kifejlesztettek egy típusváltozó-mechanizmust, amely képes leküzdeni a kovariancia és a polimorfizmus kombinálásának problémáit (még figyelmen kívül hagyva a leszármazottak elrejtésének problémáját). Ezután az osztályfejlesztőnek szüksége lesz rendkívüli intuíció annak érdekében, hogy előre eldöntsük, melyik összetevő lesz elérhető a típusok újradefiniálásához a származtatott osztályokban, és melyik nem. Az alábbiakban ezt a problémát tárgyaljuk, amely a programkészítés gyakorlatában jelentkezik, és sajnos sokak alkalmazhatóságát megkérdőjelezi. elméleti sémák.

Ez arra kényszerít bennünket, hogy visszatérjünk a már vizsgált mechanizmusokhoz: a korlátozott és korlátlan egyetemességhez, a típustűzéshez és természetesen az öröklődéshez.

Típus rögzítésre támaszkodva

A kovariancia problémájára szinte kész megoldást találunk, ha megvizsgáljuk a rögzített deklarációk általunk ismert mechanizmusát.

Az osztályok leírásánál SÍELŐÉs SÍLŐ1 Nem tehetett róla, hogy meglátogat a vágy, hogy a rögzített bejelentések segítségével megszabaduljon sok újrafogalmazástól. A rögzítés egy tipikus kovariáns mechanizmus. Így nézne ki a példánk (minden változtatás aláhúzva):


megosztás (egyéb: mint Aktuális) is ... igényel ... do
accommodate (r: mint szállás) is ... megkövetel ... do

Most már a gyerekek elhagyhatják az osztályt SÍELŐ változatlan, és SÍLŐ1 csak felül kell írniuk az attribútumot szállás. Rögzített entitások: attribútum szobatársés szubrutin argumentumok Ossza megÉs befogadni- automatikusan megváltozik. Ez nagymértékben leegyszerűsíti a munkát, és megerősíti azt a tényt, hogy rögzítés (vagy más hasonló mechanizmus, például típusváltozók) hiányában lehetetlen valósághű gépeléssel OO terméket írni.

De sikerült kiküszöbölnie a rendszer helyességének megsértését? Nem! A típusellenőrzést a korábbiakhoz hasonlóan túlszárnyalhatjuk, ha olyan polimorf hozzárendeléseket végzünk, amelyek megsértik a rendszer helyességét.

Igaz, a példák eredeti változatait elutasítják. Legyen:


Create b;create g;-- Fiú és LÁNY objektumok létrehozása.
s:=b; -- Polimorf feladat.

Érv g továbbított Ossza meg, most hibás, mert egy típusú objektumot igényel mint s, és az osztály LÁNY nem kompatibilis ezzel a típussal, mivel a rögzített típusok szabálya szerint egyik típus sem kompatibilis mint s kivéve önmagát.

Mi azonban nem sokáig örülünk. Másrészt ez a szabály azt mondja mint s típussal kompatibilis s. Tehát nem csak az objektum polimorfizmusát használva s, hanem a paraméter is g, ismét megkerülhetjük a típusellenőrző rendszert:


s: SÍLŐ; b:FIÚ; g: tetszik; tényleges_g:LÁNY;
létrehoz b; aktuál_g létrehozása -- BOY és GIRL objektumok létrehozása.
s:= tényleges_g; g:= s -- G hozzáfűzése a GIRL-hez s-en keresztül.
s:= b -- Polimorf hozzárendelés.

Ennek eredményeként az illegális hívás átmegy.

Van kiút. Ha komolyan gondoljuk, hogy a deklarációs rögzítést használjuk a kovariancia egyetlen mechanizmusaként, akkor a rögzített entitás polimorfizmusának teljes tiltásával megszabadulhatunk a szisztémás korrektség megsértésétől. Ehhez meg kell változtatni a nyelvet: vezessen be egy új kulcsszót horgony(erre a hipotetikus konstrukcióra csak azért van szükségünk, hogy ebben a vitában használhassuk):


Engedjük meg az olyan nyilatkozatokat, mint mint s csak akkor, ha s mint horgony. Változtassuk meg a kompatibilitási szabályokat, hogy biztosítsuk: sés típusú elemek mint s csak egymáshoz köthetők (feladatokban vagy érvtovábbításban).

Ezzel a megközelítéssel eltávolítjuk a nyelvből az esetleges szubrutin argumentumok típusának újradefiniálásának lehetőségét. Ezenkívül megtilthatjuk az eredménytípus újradefiniálását, de ez nem szükséges. Az attribútumtípus felülírásának lehetősége természetesen megmarad. Minden Az argumentumtípusok újradefiniálása implicit módon a kovariancia által kiváltott rögzítési mechanizmuson keresztül történik. Ahol az előző megközelítéssel az osztály D felülírja az örökölt összetevőt a következőképpen:


míg az osztály C- szülő Dúgy nézett ki


Ahol Y levelezett x, most újradefiniálja az összetevőt rígy fog kinézni:


Az osztályteremben marad D felülírás típusa te_horgonyod.

A kovariancia-polimorfizmus problémájának ezt a megoldását megközelítésnek nevezzük Lehorgonyzás. Helyesebb lenne azt mondani: "Kovariancia csak a rögzítés révén". A megközelítés tulajdonságai vonzóak:

[x]. A rögzítés a szigorú elválasztás gondolatán alapul kovariánsés potenciálisan polimorf (vagy röviden polimorf) elemek. Minden entitás, mint horgony vagy mint valami_horgony kovariáns; mások polimorfok. Mind a két kategóriában bármilyen melléklet megengedett, de nincs olyan entitás vagy kifejezés, amely megsérti a határt. Nem rendelhet például polimorf forrást egy kovariáns célhoz.

[x]. Ezt az egyszerű és elegáns megoldást még a kezdőknek is könnyű elmagyarázni.

[x]. Teljesen kiküszöböli a rendszerhelyesség megsértésének lehetőségét a kovariánsan felépített rendszerekben.

[x]. Megtartja a fentebb lefektetett fogalmi keretet, beleértve a korlátozott és korlátlan egyetemesség fogalmát. (Ennek eredményeként véleményem szerint ez a megoldás előnyösebb, mint a kovariancia és az univerzalitás mechanizmusát felváltó, különféle gyakorlati problémák megoldására hivatott típusváltozók.)

[x]. Kisebb nyelvi változtatást igényel - egyetlen kulcsszó hozzáadása, amely tükröződik az egyezési szabályban -, és nem jár észrevehető megvalósítási nehézségekkel.

[x]. Reális (legalábbis elméletben): bármely korábban lehetséges rendszer átírható, ha a kovariáns felülírásokat lehorgonyzott újradeklarációkkal helyettesítjük. Igaz, hogy egyes mellékletek emiatt érvénytelenek lesznek, de olyan eseteknek felelnek meg, amelyek típussértéshez vezethetnek, ezért ki kell cserélni hozzárendelési kísérletekkel, és futás közben kell kezelni.

Úgy tűnik, a vita ezzel véget is érhet. Akkor miért nem teljesen tetszik a lehorgonyzási megközelítés? Először is, még nem érintettük a gyerekrejtőzés kérdését. Emellett a vita folytatásának fő oka a típusváltozók rövid említésében már megfogalmazott probléma. A befolyási övezetek polimorf és kovariáns részre osztása némileg hasonló a jaltai konferencia eredményéhez. Feltételezi, hogy az osztály tervezőjének rendkívüli intuíciója van, hogy képes minden egyes bevezetett entitásra, különösen minden érvre, hogy egyszer s mindenkorra válasszon egyet a két lehetőség közül:

[x]. Egy entitás potenciálisan polimorf: most vagy később (paraméterek átadásával vagy hozzárendeléssel) olyan objektumhoz kapcsolható, amelynek típusa eltér a deklarálttól. Az eredeti entitástípust az osztály egyik leszármazottja sem módosíthatja.

[x]. Egy entitás a típusfelülírás tárgya, ami azt jelenti, hogy vagy lehorgonyzott, vagy maga is pivot.

De hogyan láthatja mindezt előre egy fejlesztő? Az OO-módszer minden vonzereje, amely sok szempontból a Nyitott-Zárt elvben kifejeződik, éppen annak köszönhető, hogy a korábban elvégzett munkákon jogunkban áll a változtatások lehetősége, valamint az, hogy az univerzális megoldások fejlesztője Nem végtelen bölcsességgel kell rendelkeznie, meg kell értenie, hogyan tudják termékét az utódok igényeihez igazítani.

Ezzel a megközelítéssel a típusok újradefiniálása és a leszármazottak általi elrejtés egyfajta "biztonsági szelep", amely lehetővé teszi egy meglévő, a céljainknak szinte megfelelő osztály újrafelhasználását:

[x]. A típus újradefiniálásával megváltoztathatjuk a származtatott osztály deklarációit anélkül, hogy az eredetit befolyásolnánk. Ebben az esetben egy tisztán kovariáns megoldás az eredeti szerkesztését igényli a leírt átalakításokkal.

[x]. A leszármazottak elrejtése sok kudarc ellen megvéd az osztály létrehozásakor. Lehet kritizálni egy olyan projektet, amelyben TÉGLALAP, kihasználva azt a tényt leszármazottja POLIGON, megpróbál hozzáadni egy csúcsot. Ehelyett lehetne javasolni egy olyan öröklődési struktúrát, amelyben a rögzített számú csúcsú figurák el vannak választva az összes többitől, és a probléma nem merülne fel. Az öröklődési struktúrák tervezésekor azonban mindig előnyben kell részesíteni azokat, amelyek nem rendelkeznek taxonómiai kivételek. De vajon teljesen kiküszöbölhetők? Ha a következő előadások egyikében az exportkorlátozásokat tárgyaljuk, látni fogjuk, hogy ez két okból nem lehetséges. Az első a versengő besorolási kritériumok jelenléte. Másodszor annak a valószínűsége, hogy a fejlesztő nem találja meg az ideális megoldást, még ha létezik is.

Globális elemzés

Ez a rész a köztes megközelítés leírására szolgál. A főbb gyakorlati megoldásokat a 17. előadás mutatja be.

A rögzítési lehetőség vizsgálata során észrevettük, hogy annak fő gondolata a kovariáns és a polimorf entitáshalmaz szétválasztása volt. Tehát, ha az űrlap két utasítását vesszük


mindegyik példa a fontos OO mechanizmusok helyes alkalmazására: az első - polimorfizmus, a második - típusú újradefiniálás. A problémák akkor kezdődnek, amikor ugyanazon entitáson kombinálják őket s. Hasonlóképpen:


a problémák két független és teljesen ártatlan operátor egyesülésével kezdődnek.

A hibás hívások típussértésekhez vezetnek. Az első példában a polimorf hozzárendelés egy objektumot csatol FIÚ a lényegre s, mit csinál g hibás érvelés Ossza meg, mert egy objektumhoz kapcsolódik LÁNY. A második példában az entitáshoz r a tárgy csatolva van TÉGLALAP, ami kizárja add_vertex az exportált alkatrészekből.

Íme egy új megoldás ötlete: előre - statikusan, a típusok fordítóval vagy egyéb eszközökkel történő ellenőrzésekor - definiáljuk betűkészlet minden entitás, beleértve az objektumok típusait, amelyekhez az entitás futás közben társítható. Ezután ismét statikusan megbizonyosodunk arról, hogy minden hívás helyes a cél típusának és argumentumkészletének minden elemére.

Példáinkban az operátor s:=b jelzi, hogy az osztály FIÚ típuskészletéhez tartozik s(mert a create utasítás végrehajtásának eredményeként létrehozni b típushalmazába tartozik b). LÁNY, az utasítások jelenléte miatt létrehozni g, a típuskészlethez tartozik g. De akkor a kihívás Ossza megérvénytelen lesz a cél számára s típus FIÚés érvelés g típus LÁNY. Hasonlóképpen TÉGLALAP számára beállított típusban van p, ami polimorf hozzárendelésnek köszönhető, azonban a hívás add_vertex Mert p típus TÉGLALAPérvénytelen lesz.

Ezek a megfigyelések elgondolkodtatnak bennünket a teremtésről globális az új gépelési szabályon alapuló megközelítés:

Rendszerhelyességi szabály

Hívás x.f(arg) akkor és csak akkor rendszerhelyes, ha osztályhelyes x, És arg, amelyek bármilyen típussal rendelkeznek a megfelelő típuskészletükből.

Ebben a definícióban egy hívás akkor tekinthető osztályhelyesnek, ha nem sérti a komponenshívási szabályt, amely szerint: ha C van egy ilyen alaposztály x, komponens f exportálni kell C, és a típus arg kompatibilisnek kell lennie a formális paraméter típusával f. (Ne feledje, az egyszerűség kedvéért feltételezzük, hogy minden szubrutinnak csak egy paramétere van, de nem nehéz kiterjeszteni a szabályt tetszőleges számú argumentumra.)

A hívás rendszerhelyessége az osztályhelyességre redukálódik, azzal a különbséggel, hogy nem az egyes elemekre, hanem a halmazkészletekből származó párokra kerül ellenőrzésre. Íme az egyes entitásokhoz tartozó típuskészlet létrehozásának alapvető szabályai:

1 Minden entitás esetében a kezdeti típuskészlet üres.

2 Az űrlap másik utasításával találkozva létrehozás(SOME_TYPE) a, add hozzá SOME_TYPE a típuskészlethez a. (Az egyszerűség kedvéért feltételezzük, hogy bármely utasítás hozzon létre egy utasítás váltja fel létrehozni (ATÍPUS) a, Ahol EGY TÍPUS- entitás típusa a.)

3 Találkozás az űrlap másik feladatával a:=b, add hozzá a típuskészlethez a számára a b.

4 Ha a a szubrutin formális paramétere, akkor, miután a következő hívás találkozott az aktuális paraméterrel b, add hozzá a típuskészlethez a számára a a típuskészlet összes eleme b.

5 Addig ismételjük a (3) és (4) lépéseket, amíg a típuskészletek változása meg nem szűnik.

Ez a megfogalmazás nem veszi figyelembe az univerzalitás mechanizmusát, azonban lehetőség van a szabály szükség szerinti kiterjesztésére különösebb probléma nélkül. Az (5) lépés szükséges a hozzárendelési és áthelyezési láncok lehetősége miatt (tól b Nak nek a, tól től c Nak nek b stb.). Könnyen megérthető, hogy véges számú lépés után ez a folyamat leáll.

Amint azt észrevette, a szabály nem veszi figyelembe az utasítássorozatokat. Amikor


létrehoz(TYPE1) t; s:=t; létrehozni (TYPE2) t

a típuskészlethez sírja be mint TÍPUS1, és TÍPUS2, Habár s Az utasítások sorrendjét figyelembe véve csak az első típusú értékeket képes felvenni. Az utasítások helyének figyelembevétele megköveteli, hogy a fordító alaposan elemezze az utasításfolyamot, ami az algoritmus bonyolultságának túlzott növekedéséhez vezet. Ehelyett pesszimistább szabályok érvényesek: a műveletek sorrendje:


rendszerhibásnak nyilvánítják, annak ellenére, hogy a végrehajtásuk sorrendje nem vezet típussértéshez.

A rendszer globális elemzését (részletesebben) a monográfia 22. fejezete mutatta be. Ugyanakkor megoldódott a kovariancia problémája és az öröklődés során fennálló exportkorlátozások problémája is. Ennek a megközelítésnek azonban van egy sajnálatos gyakorlati hibája, nevezetesen: állítólag ellenőrizni kell rendszer egészét nem pedig minden osztály külön-külön. A (4) szabály végzetesnek bizonyul, amely egy könyvtári szubrutin meghívásakor figyelembe veszi annak összes lehetséges hívását más osztályokban.

Bár az egyes osztályokkal való munkavégzéshez későbbi algoritmusokat javasoltak ben, gyakorlati értéküket nem sikerült megállapítani. Ez azt jelentette, hogy a növekményes fordítást támogató programozási környezetben a teljes rendszer ellenőrzését meg kell szervezni. Kívánatos bevezetni az ellenőrzést, mint a felhasználó által egyes osztályokon végrehajtott változtatások (gyors) helyi feldolgozásának elemét. Bár ismertek példák a globális megközelítésre, például a C programozók használják az eszközt szösz olyan következetlenségeket találni a rendszerben, amelyeket a fordító nem észlel - mindez nem tűnik túl vonzónak.

Ebből kifolyólag a rendszerhelyesség-ellenőrzést tudtommal senki nem valósította meg. (Egy másik oka ennek az eredménynek maguknak az érvényesítési szabályoknak a bonyolultsága lehetett.)

Az osztály helyessége magában foglalja az osztályra korlátozódó érvényesítést, ezért növekményes fordítással lehetséges. A rendszer helyessége magában foglalja a teljes rendszer globális ellenőrzését, ami ütközik a növekményes fordítással.

Azonban a neve ellenére a rendszer helyességét csak növekményes osztályellenőrzéssel lehet ellenőrizni (egy normál fordító során). Ez lesz a végső hozzájárulás a probléma megoldásához.

Óvakodjunk a polimorf macskahívásoktól!

A rendszerhelyességi szabály pesszimista: az egyszerűség kedvéért elutasítja a teljesen biztonságos utasításkombinációkat is. Bármilyen paradoxnak is tűnik, de ez alapján fogjuk megszerkeszteni a megoldás utolsó változatát még pesszimistább szabály. Ez természetesen felveti a kérdést, hogy mennyire lesz reális az eredményünk.

Vissza Jaltába

A megoldás lényege Catcall (Catcall), - ennek a fogalomnak a jelentését később kifejtjük - visszatérve a jaltai megállapodások szelleméhez, a világot polimorfra és kovariánsra osztva (és a kovariancia társára - utódokat rejtve), de a végtelen bölcsesség birtoklása nélkül.

A kovariancia kérdését a korábbiakhoz hasonlóan két műveletre szűkítjük. Fő példánkban ez egy polimorf hozzárendelés: s:=b, és a kovariáns szubrutin meghívása: s.share(g). Elemezve, hogy ki a jogsértések valódi tettese, kizárjuk az érvelést g a gyanúsítottak közül. Bármilyen típusú argumentum SÍELŐ vagy abból származik, polimorfizmus miatt nem felel meg nekünk sés kovariancia Ossza meg. Ezért ha statikusan leírod a lényeget Egyéb Hogyan SÍELŐés dinamikusan csatlakozik az objektumhoz SÍELŐ, majd a hívás s.share (egyéb) statikusan ideális benyomást kelt, de típussértéseket eredményez, ha polimorf módon hozzárendeljük s jelentése b.

Az alapvető probléma az, hogy megpróbáljuk használni s két összeférhetetlen módon: polimorf entitásként és egy kovariáns szubrutinhívás célpontjaként. (A másik példánkban a probléma a használat p polimorf entitásként és egy gyermek szubrutinjának meghívásának célpontjaként, amely elrejti az összetevőt add_vertex.)

A Catcall megoldása a Pinninghez hasonlóan radikális: tiltja, hogy egy entitást egyszerre polimorfként és kovariánsként használjunk. A globális elemzéshez hasonlóan statikusan meghatározza, hogy mely entitások lehetnek polimorfok, de nem próbál túl okoskodni azzal, hogy lehetséges típuskészleteket keres az entitásokhoz. Ehelyett minden polimorf entitást eléggé gyanúsnak érzékelnek ahhoz, hogy tilos szövetségre lépni tiszteletreméltó személyek körével, beleértve a kovarianciát és a leszármazottak elrejtését.

Egy szabály és több definíció

A Catcall megoldás típusszabálya egyszerű megfogalmazással rendelkezik:

Írja be a Catcall szabályt

A polimorf macskahívások helytelenek.

Ugyanilyen egyszerű definíciókon alapul. Először is egy polimorf entitás:

Definíció: polimorf entitás

Lényeg x A referencia (nem kiterjesztett) típus polimorf, ha rendelkezik az alábbi tulajdonságok egyikével:

1 Feladatban fordul elő x:= y, ahol a lényeg y más típusú vagy rekurziós polimorf.

2 A létrehozási útmutatóban található létrehozás (OTHER_TYPE) x, Ahol OTHER_TYPE nem a nyilatkozatban megadott típus x.

3 Ez egy formális érv egy szubrutinhoz.

4 Is külső funkció.

Ennek a definíciónak az a célja, hogy polimorf ("potenciálisan polimorf") státuszt adjon minden olyan entitásnak, amely a program végrehajtása során különböző típusú objektumokhoz kapcsolódhat. Ez a meghatározás csak referenciatípusokra vonatkozik, mivel a kiterjesztett entitások természetüknél fogva nem lehetnek polimorfok.

Példánkban a síelő sés sokszög p az (1) szabály szerint polimorfok. Az elsőhöz egy objektum van hozzárendelve FIÚ b, a második - az objektum TÉGYSZÖG r.

Ha megnézi egy típuskészlet meghatározását, észre fogja venni, hogy egy polimorf entitás meghatározása mennyivel pesszimistább, és mennyivel könnyebb tesztelni. Anélkül, hogy megpróbálnánk megtalálni egy entitás összes lehetséges dinamikus típusát, megelégszünk az általános kérdéssel: lehet-e egy adott entitás polimorf vagy sem? A legmeglepőbb a (3) szabály, amely szerint polimorf számít minden formális paraméter(kivéve, ha a típusa kiterjesztett, mint az egész számok esetében stb.). Még csak nem is foglalkozunk híváselemzéssel. Ha az alprogramnak van argumentuma, akkor az teljes mértékben a kliens rendelkezésére áll, ami azt jelenti, hogy nem támaszkodhat a deklarációban megadott típusra. Ez a szabály szorosan kapcsolódik az újrafelhasználáshoz – az objektumtechnológia céljához –, ahol bármely osztály potenciálisan bekerülhet egy könyvtárba, és többször is meghívható a különböző kliensek által.

A szabály jellemző tulajdonsága, hogy nem igényel globális ellenőrzéseket. Egy entitás polimorfizmusának azonosításához elegendő magát az osztály szövegét megnézni. Ha minden kérésnél (attribútumnál vagy függvénynél) tárolódnak a polimorf állapotukra vonatkozó információk, akkor még az ősök szövegeit sem kell tanulmányozni. A típushalmazok keresésével ellentétben a polimorf entitásokat úgy fedezheti fel, hogy osztályonként ellenőrzi a növekményes fordítás során.

A hívások, az entitásokhoz hasonlóan, lehetnek polimorfok:

Definíció: polimorf hívás

Egy hívás akkor polimorf, ha a célpontja polimorf.

Példánkban mindkét hívás polimorf: s.share(g) polimorfizmus miatt s, p.add_vertex(...) polimorfizmus miatt p. Definíció szerint csak a minősített hívások lehetnek polimorfok. (Korlátlan hívás f(...) amolyan képzett Current.f(...), a dolog lényegén nem változtatunk, hiszen Jelenlegi, amelyhez semmit nem lehet hozzárendelni, nem polimorf objektum.)

Ezután szükségünk van a Catcall koncepciójára, amely a CAT koncepcióján alapul. (A CAT az elérhetőség vagy típus megváltoztatása rövidítése). Egy szubrutin CAT-szubrutin, ha egy gyermek általi újradefiniálása kétféle változás egyikét eredményezi, amelyekről azt láttuk, hogy potenciálisan veszélyesek: az argumentum típusának megváltoztatása (kovariánsan) vagy egy korábban exportált komponens elrejtése.

Definíció: CAT-rutinok

Egy rutint CAT-rutinnak nevezünk, ha valamilyen újradefiniálása megváltoztatja az exportállapotot vagy bármely argumentuma típusát.

Ez a tulajdonság ismét lehetővé teszi a növekményes ellenőrzést: az argumentumtípus vagy az exportállapot újradefiniálása az eljárást vagy funkciót CAT-szubrutinná teszi. Itt jön képbe Catcall elképzelése: olyan CAT szubrutin meghívása, amely hibás lehet.

Definíció: Catcall

Egy hívást Cathívásnak nevezünk, ha a rutin valamilyen újradefiniálása meghiúsítaná az exportállapot vagy az argumentumtípus megváltozása miatt.

Az általunk létrehozott osztályozás lehetővé teszi a hívások speciális csoportjainak megkülönböztetését: polimorf és kacsahívásokat. A polimorf hívások kifejező erőt adnak az objektum-megközelítésnek, a catcall-ok pedig lehetővé teszik a típusok újradefiniálását és az exportálás korlátozását. A fejezetben korábban bemutatott terminológiával azt mondhatjuk, hogy a polimorf hívások kiterjednek hasznosság, macskahívások - használhatóság.

Kihívások Ossza megÉs add_vertex A példáinkban figyelembe vett macskahívások. Az első végrehajtja az argumentum kovariáns újradefiniálását. A másodikat az osztály exportálja TÉGLALAP, de az osztály elrejtette POLIGON. Mindkét hívás polimorf is, így kiváló példái a polimorf macskahívásoknak. A Catcall típusú szabály szerint hibásak.

Fokozat

Mielőtt összefoglalnánk mindazt, amit a kovarianciáról és a gyermekrejtőzésről tanultunk, ismételjük meg, hogy a rendszerek helyességének megsértése valóban ritka. A statikus OO tipizálás legfontosabb tulajdonságait az előadás elején összefoglaltuk. A gépelési mechanizmusok lenyűgöző tömbje az osztályalapú érvényesítéssel együtt megnyitja az utat a biztonságos és rugalmas szoftverkészítési módszer előtt.

A kovariancia problémájára három megoldást láttunk, amelyek közül kettő az exportkorlátozásokat is érintette. Melyik a helyes?

Erre a kérdésre nincs végleges válasz. Az OO tipizálás és a polimorfizmus közötti alattomos kölcsönhatás következményeit nem értjük annyira, mint a korábbi előadásokban tárgyalt kérdéseket. Az elmúlt években számos publikáció jelent meg e témában, amelyekre hivatkozást az előadás végén található irodalomjegyzék tartalmazza. Emellett remélem, hogy ezen az előadáson sikerült bemutatnom a végső megoldás elemeit, vagy legalább közel kerülni hozzá.

A globális elemzés a teljes rendszer teljes ellenőrzése miatt nem tűnik praktikusnak. Ez azonban segített nekünk jobban megérteni a problémát.

A Pinning megoldás rendkívül vonzó. Egyszerű, intuitív, könnyen megvalósítható. Annál is inkább sajnálnunk kell, hogy nem tudjuk támogatni benne az OO-módszer számos kulcsfontosságú követelményét, amelyek a nyitott-zárt elvben is tükröződnek. Ha valóban remek intuíciónk lenne, akkor a rögzítés remek megoldás lenne, de melyik fejlesztő merné ezt kijelenteni, vagy még inkább beismerni, hogy a projektjében örökölt könyvtári órák szerzői ilyen megérzésekkel rendelkeztek?

Ha kénytelenek vagyunk elhagyni a rögzítést, akkor a Catcall megoldás tűnik a legmegfelelőbbnek, ami meglehetősen könnyen magyarázható és a gyakorlatban is alkalmazható. Pesszimizmusa nem zárhatja ki az operátorok hasznos kombinációit. Abban az esetben, ha egy polimorf hívást egy "jogos" kijelentés generál, mindig biztonságosan be lehet ismerni egy hozzárendelési kísérlet bevezetésével. Így számos ellenőrzést át lehet vinni a program végrehajtási idejére. Az ilyen esetek számának azonban rendkívül kicsinek kell lennie.

Pontosításként meg kell jegyeznem, hogy az írás idején a Catcall megoldása még nem valósult meg. Amíg a fordító nincs adaptálva a Catcall típusszabály-ellenőrzésére, és sikeresen alkalmazzák kicsi és nagy reprezentációs rendszerekre, még korai azt állítani, hogy a statikus tipizálás és a polimorfizmus összeegyeztetésének problémája kimondta volna az utolsó szót, kombinálva a kovarianciával és a leszármazottak elrejtésével.

Teljes megfelelés

A kovariancia tárgyalását befejezve hasznos megérteni, hogyan alkalmazható egy általános módszer egy meglehetősen általános problémára. A módszer a Catcall elmélet eredményeként jelent meg, de a nyelv alapváltozatának keretein belül is használható új szabályok bevezetése nélkül.

Legyen két egyező lista, ahol az első a síelőket, a második pedig a szobatársat adja meg az első listából. A megfelelő elhelyezési eljárást szeretnénk lefolytatni Ossza meg, csak akkor, ha ezt lehetővé teszik a típusleírási szabályok, amelyek lehetővé teszik a lányok lányokkal, a lányok díjazása díjazott lányokkal stb. Az ilyen jellegű problémák gyakoriak.

A korábbi megbeszélés és feladatkiosztási kísérlet alapján egyszerű megoldás is lehet. Tekintsük az univerzális funkciót szerelt(jóváhagy):


felszerelt (egyéb: ÁLTALÁNOS): mint más is
-- Az aktuális objektum (Current), ha típusa megegyezik az objektum típusával,
-- máshoz csatolva, egyébként érvénytelen.
if other /= Void and then conforms_to (egyéb) then

Funkció szerelt az aktuális objektumot adja vissza, de az argumentumhoz csatolt típusú entitásként ismert. Ha az aktuális objektum típusa nem egyezik az argumentumhoz csatolt objektum típusával, akkor visszatér Üres. Jegyezze meg a hozzárendelési kísérlet szerepét. A függvény az összetevőt használja megfelel_nek osztályból TÁBORNOK, amely egy objektumpár típuskompatibilitását határozza meg.

Csere megfelel_nek másik komponensre TÁBORNOK Névvel azonos_típus funkciót ad nekünk tökéletesen_fitted (teljes megfelelés), amely visszaadja Üres ha a két objektum típusa nem azonos.

Funkció szerelt- egyszerű megoldást ad a síelők párosításának problémájára a típusleírás szabályainak megszegése nélkül. Tehát az osztálykódban SÍELŐ bevezethetünk egy új eljárást és használhatjuk helyette Ossza meg, (ez utóbbi rejtett eljárássá tehető).


-- Ha van, válasszon mást számszomszédként.
-- gender_scertained - gender hozzárendelve
gender_acertained_other: mint Aktuális
gender_acertained_other:= egyéb .fitted(Jelenlegi)
if gender_acertained_other /= Érvénytelen akkor
megosztás (nem_meghatározott_egyéb)
"Következtetés: Másokkal való elhelyezés nem lehetséges"

Mert Egyéb tetszőleges típus SÍELŐ(nem csak mint a Current) határozza meg a verziót nem_meghatározott_egyéb, amelynek típusa hozzá van rendelve Jelenlegi. A funkció segít a típusok azonosságának garantálásában tökéletesen_fitted.

Ha a tervezett szállást két párhuzamos síelőlista képviseli:


occupant1, occupant2: LIST

lehetőség van egy hurok megszervezésére úgy, hogy minden lépésben hívást hajt végre:


occupant1.item.safe_share(occupant2.item)

egyező listaelemek akkor és csak akkor, ha típusaik teljesen kompatibilisek.

Kulcsfogalmak

[x]. A statikus gépelés a megbízhatóság, az olvashatóság és a hatékonyság kulcsa.

[x]. A valósághűség érdekében a statikus gépelés a mechanizmusok kombinációját igényli: állítások, többszörös öröklődés, hozzárendelési kísérlet, korlátos és korlátlan általánosság, rögzített deklarációk. A típusrendszer nem engedheti meg a csapdákat (típusöntvényeket).

[x]. Az újradeklaráció hüvelykujjszabályainak lehetővé kell tenniük a kovariáns újradefiniálást. A felülírt eredmény- és argumentumtípusoknak kompatibilisnek kell lenniük az eredetivel.

[x]. A kovariancia, valamint az a képesség, hogy egy gyermek elrejtse az őse által exportált komponenst, kombinálva a polimorfizmussal, ritka, de nagyon súlyos típussértési problémát okoz.

[x]. Ezek a jogsértések elkerülhetők a következők használatával: globális elemzés (ami nem praktikus), a kovariancia fix típusokra való korlátozása (ami ellentétes a nyitott-zárt elvvel), a Catcall megoldása, amely megakadályozza, hogy egy polimorf célpont kovarianciával hívjon meg egy szubrutint vagy rejtsen el egy szubrutint. gyermek.

Bibliográfiai jegyzetek

Ennek az előadásnak számos anyaga bemutatásra kerül az OOPSLA 95 és a TOOLS PACIFIC 95 fórumokon készült beszámolókban, és ben is megjelent. Számos áttekintési anyagot kölcsönöztünk a cikkből.

Az automatikus típuskövetkeztetés fogalmát ben vezették be, ahol a funkcionális ML nyelv típuskövetkeztetési algoritmusát írják le. A polimorfizmus és a típusellenőrzés kapcsolatát ban tárták fel.

A dinamikusan tipizált nyelvek kódhatékonyságának javítására szolgáló technikák az Self nyelv kontextusában a következő helyen találhatók: .

Luca Cardelli és Peter Wegner elméleti cikket írt a programozási nyelvek típusairól, amelyek nagy hatással voltak a szakemberekre. Ez a lambda-kalkulus (ld.) alapján felépített munka számos további tanulmány alapjául szolgált. Cardelli egy másik alapvető írása előzte meg.

Az ISE kézikönyv bevezetőt tartalmaz a polimorfizmus, a kovariancia és a leszármazottak elrejtésének együttes alkalmazásának problémáiba. A megfelelő elemzés hiánya a könyv első kiadásában számos kritikai vitához vezetett (amelyek közül az első Philippe Elinck megjegyzései voltak a „De la Conception-Programmation par Objets”, Memoire de licence, Universite Libre de című alapmunkájában. Bruxelles (Belgium), 1988), a munkákban és a . Cook cikke számos példát ad a kovariancia-problémával kapcsolatban, és kísérletet tesz annak megoldására. A TOOLS EUROPE 1992-ben a kovariáns entitások típusparaméterein alapuló megoldást Franz Weber javasolta. A rendszerhelyesség, valamint az osztályhelyesség fogalmának precíz definíciói a -ban találhatók, és egy teljes rendszerelemzést használó megoldást is javasoltak ott. A Catcall megoldást először ben javasolták; Lásd még .

A Fixing megoldást a TOOLS EUROPE 1994 szemináriumon tartott előadásomban mutatták be, de akkor még nem láttam szükségét annak horgony-hirdetések és a kapcsolódó kompatibilitási korlátozások. Paul Dubois és Amiram Yehudai gyorsan rámutatott, hogy a kovariancia problémája ilyen körülmények között is fennáll. Ők, valamint Reinhardt Budde, Karl-Heinz Sylla, Kim Walden és James McKim számos olyan megjegyzést tettek, amelyek alapvető fontosságúak voltak az előadás megírásához vezető munkában.

A kovariancia kérdéseivel nagy mennyiségű irodalom foglalkozik. A és az oldalon egy kiterjedt bibliográfiát és egy áttekintést talál a probléma matematikai vonatkozásairól. Az OOP-típuselméletről szóló online anyagokra és szerzőik weboldalaira mutató hivatkozások listáját lásd Laurent Dami oldalán. A kovariancia és kontravariancia fogalmát a kategóriaelméletből kölcsönöztük. A programgépelés kapcsán való megjelenésüket Luca Cardellinek köszönhetjük, aki a 80-as évek elején kezdte használni beszédeiben, de nyomtatásban csak a 80-as évek végén nyúlt hozzájuk.

A típusváltozókon alapuló technikákat a , , .

A kontravarianciát a Sather nyelvben valósították meg. A magyarázatokat a.

  • A dinamikus gépelés a programozási nyelvekben és a specifikációs nyelvekben széles körben használt technika, amelyben egy változó az érték hozzárendelésekor van társítva egy típushoz, nem pedig a változó deklarálásakor. Így a program különböző részeiben ugyanaz a változó különböző típusú értékeket vehet fel. Példák a dinamikusan beírt nyelvekre: Smalltalk, Python, Objective-C, Ruby, PHP, Perl, JavaScript, Lisp, xBase, Erlang, Visual Basic.

    Az ellenkező technika a statikus gépelés.

    Egyes nyelveken, ahol a dinamikus gépelés gyenge, probléma van az értékek összehasonlításával, például a PHP-ben az "==", "!=" és "===", "!=="" összehasonlító operátorok vannak, ahol a A második műveletpár összehasonlítja az értékeket és a változók típusait. A "===" operátor csak akkor értékel igazra, ha tökéletesen egyezik, ellentétben a "=="-val, amely a következő kifejezést tekinti igaznak: (1=="1"). Érdemes megjegyezni, hogy ez nem általában a dinamikus gépelés problémája, hanem bizonyos programozási nyelvek problémája.

Kapcsolódó fogalmak

A programozási nyelv a számítógépes programok írásának formális nyelve. A programozási nyelv lexikai, szintaktikai és szemantikai szabályok összességét határozza meg, amelyek meghatározzák a program megjelenését és azokat a műveleteket, amelyeket az előadó (általában számítógép) irányítása alatt hajt végre.

A szintaktikai cukor egy programnyelvben egy szintaktikai jellemző, amelynek használata nem befolyásolja a program viselkedését, de kényelmesebbé teszi a nyelv használatát az ember számára.

A tulajdonság egy mód az objektum belső állapotához való hozzáférésre, valamilyen típusú változót utánozva. Egy objektum egy tulajdonságának elérése ugyanúgy néz ki, mint egy struct mező elérése (a strukturált programozásban), de valójában függvényhíváson keresztül valósul meg. Amikor megpróbálja beállítani ennek a tulajdonságnak az értékét, egy metódus hívódik meg, és amikor megpróbálja lekérni ennek a tulajdonságnak az értékét, egy másik metódus hívódik meg.

Az Extended Backus–Naur Form (EBNF) egy formális szintaktikai definíciós rendszer, amelyben egyes szintaktikai kategóriák szekvenciálisan vannak meghatározva másokon keresztül. Kontextusmentes formális nyelvtan leírására szolgál. Niklaus Wirth javasolta. Ez a Backus-Naur formák kiterjesztett átdolgozása, a BNF-től "terjedtebb" konstrukciókban különbözik, amelyek ugyanazzal a kifejezőképességgel teszik lehetővé az egyszerűsítést...

Az applikatív programozás a deklaratív programozás egyik fajtája, amelyben egy program megírása az egyik objektum másikra történő szisztematikus alkalmazásából áll. Egy ilyen alkalmazás eredménye ismét egy objektum, amely funkcióként és argumentumként is részt vehet az alkalmazásokban stb. Ez matematikailag egyértelművé teszi a program rögzítését. Az a tény, hogy egy függvényt kifejezéssel jelölünk, az értékfüggvények használatának lehetőségét jelzi - funkcionális ...

A konkatenatív programozási nyelv olyan programozási nyelv, amely azon alapul, hogy két kódrészlet összefűzése fejezi ki azok összetételét. Egy ilyen nyelvben széles körben használják a függvényargumentumok implicit specifikációját (lásd: értelmetlen programozás), az új függvényeket függvényösszetételként definiálják, és az alkalmazás helyett az összefűzést alkalmazzák. Ez a megközelítés szemben áll az applikatív programozással.

A változó egy fizikai vagy absztrakt rendszer attribútuma, amely megváltoztathatja annak, általában numerikus értékét. A változó fogalmát széles körben használják olyan területeken, mint a matematika, a természettudományok, a mérnöki tudományok és a programozás. Példák a változókra: levegő hőmérséklet, funkcióparaméter és még sok más.

A szintaktikai elemzés (vagy parsing, szlengelemzés ← angol elemzés) a nyelvészetben és a számítástechnikában egy természetes vagy formális nyelv lexémáinak (szavainak, jelzőinek) lineáris sorozatának összehasonlítása formális nyelvtanával. Az eredmény általában egy elemző fa (szintaktikai fa). Általában a lexikális elemzéssel együtt használják.

Az általánosított algebrai adattípus (GADT) az algebrai adattípusok egyik típusa, amelyre jellemző, hogy a konstruktőrei olyan értékeket is visszaadhatnak, amelyek nem a hozzájuk tartozó típushoz tartoznak. Az induktív családokról szóló munkák hatására készült, függő típusok kutatói körében.

A szemantika a programozásban egy olyan tudományág, amely a programnyelvi konstrukciók jelentéseinek formalizálását vizsgálja formális matematikai modelljeik megalkotásával. Az ilyen modellek felépítéséhez különféle eszközök használhatók, például matematikai logika, λ-számítás, halmazelmélet, kategóriaelmélet, modellelmélet, univerzális algebra. Egy programozási nyelv szemantikájának formalizálása egyaránt használható a nyelv leírására, a nyelv tulajdonságainak meghatározására...

Az objektum-orientált programozás (OOP) egy programozási módszertan, amely egy program objektumok halmazaként való ábrázolásán alapul, amelyek mindegyike egy bizonyos osztály példánya, és az osztályok öröklési hierarchiát alkotnak.

Dinamikus változó - egy változó a programban, egy hely a RAM-ban, amelyhez a program végrehajtása során allokálnak. Valójában ez egy memóriadarab, amelyet a rendszer a program futása közben meghatározott célokra lefoglal egy program számára. Ebben különbözik egy globális statikus változótól – egy memóriadarabtól, amelyet a rendszer a program indítása előtt meghatározott célokra lefoglal egy program számára. A dinamikus változó a változó tárolási osztályok egyike.

Annak érdekében, hogy két teljesen különböző technológiát a lehető legegyszerűbben elmagyarázhassunk, kezdjük elölről. Az első dolog, amivel a programozó találkozik kódírás közben, a változók deklarálása. Észreveheti, hogy például a C++ programozási nyelvben meg kell adni a változó típusát. Azaz, ha deklarálunk egy x változót, akkor hozzá kell adni az int - egész számok tárolására, float - lebegőpontos adatok tárolására, char - karakteres adatok tárolására és egyéb elérhető típusokat. Ezért a C++ statikus gépelést használ, akárcsak elődje, a C.

Hogyan működik a statikus gépelés?

Egy változó deklarálása pillanatában a fordítónak tudnia kell, hogy mely függvényeket és paramétereket használhatja vele kapcsolatban, és melyeket nem. Ezért a programozónak azonnal egyértelműen jeleznie kell a változó típusát. Vegye figyelembe azt is, hogy a változó típusa nem módosítható a kód futása közben. De létrehozhat saját adattípust, és használhatja a jövőben.

Nézzünk egy kis példát. Az x (int x;) változó inicializálása során megadjuk az int azonosítót - ez egy rövidítés, amelyhez csak a -2 147 483 648 és 2 147 483 647 közötti tartományban lévő egész számokat tárolja. Így a fordító megérti, hogy képes végrehajtani ennek a változónak a matematikai értékei - összeg, különbség, szorzás és osztás. De például a strcat() függvény, amely két char értéket köt össze, nem alkalmazható x-re. Végül is, ha eltávolítja a korlátozásokat, és megpróbál két int értéket összekapcsolni szimbolikus módszerrel, akkor hiba történik.

Miért van szükségünk dinamikus gépeléssel rendelkező nyelvekre?

Bizonyos korlátozások ellenére a statikus gépelésnek számos előnye van, és nem okoz különösebb kényelmetlenséget az írási algoritmusok számára. Különböző célokra azonban szükség lehet az adattípusokra vonatkozó "lazább szabályokra".

Jó példa erre a JavaScript. Ezt a programozási nyelvet általában keretrendszerbe való beágyazásra használják, hogy funkcionális hozzáférést kapjanak az objektumokhoz. Ennek a funkciónak köszönhetően nagy népszerűségre tett szert a webes technológiákban, ahol a dinamikus gépelés ideális. Időnként a kis szkriptek és makrók írása leegyszerűsödik. És van egy előnye a változók újrafelhasználásának. De ezt a lehetőséget meglehetősen ritkán használják az esetleges zavarok és hibák miatt.

Milyen típusú gépelés a legjobb?

A vita, hogy a dinamikus gépelés jobb, mint a szigorú gépelés, a mai napig tart. Általában magasan szakosodott programozóknál fordulnak elő. Természetesen a webfejlesztők nap mint nap kihasználják a dinamikus gépelés minden előnyét, hogy kiváló minőségű kódot és végső szoftverterméket hozzanak létre. Ugyanakkor az alacsony szintű programozási nyelveken bonyolult algoritmusokat fejlesztő rendszerprogramozóknak általában nincs szükségük ilyen képességekre, így számukra elegendő a statikus gépelés. Természetesen vannak kivételek a szabály alól. Például a dinamikus gépelés teljes mértékben megvalósul a Pythonban.

Ezért csak a bemeneti paraméterek alapján kell meghatározni egy adott technológia vezető szerepét. A dinamikus gépelés jobb a könnyű és rugalmas keretek fejlesztéséhez, míg az erős gépelés a masszív és összetett architektúrák létrehozásához.

Szétválasztás „erős” és „gyenge” gépelésre

Mind az orosz, mind az angol nyelvű programozási anyagok között találkozhatunk az "erős" gépelés kifejezéssel. Ez nem külön fogalom, pontosabban ilyen fogalom egyáltalán nem létezik a szakmai lexikonban. Bár sokan próbálják másképp értelmezni. Valójában az „erős” gépelés alatt azt kell érteni, amelyik kényelmes az Ön számára, és amellyel a legkényelmesebb dolgozni. A „gyenge” rendszer kényelmetlen és nem hatékony rendszer az Ön számára.

Dinamikus funkció

Biztosan észrevetted, hogy a kód írásának szakaszában a fordító elemzi az írott konstrukciókat, és hibát generál, ha az adattípusok nem egyeznek. De nem JavaScript. Különlegessége abban rejlik, hogy minden esetben elvégzi a műveletet. Íme egy egyszerű példa – egy karaktert és egy számot szeretnénk hozzáadni, aminek nincs értelme: "x" + 1.

Statikus nyelvekben magától a nyelvtől függően ennek a műveletnek különböző következményei lehetnek. De a legtöbb esetben nem is szabad fordítani, mivel a fordító azonnal hibát ad egy ilyen konstrukció megírása után. Egyszerűen helytelennek fogja tartani, és teljesen igaza lesz.

Dinamikus nyelvekben ez a művelet elvégezhető, de a legtöbb esetben hiba következik be már a kódvégrehajtási szakaszban, mivel a fordító nem elemzi valós időben az adattípusokat, és nem tud dönteni ezen a területen. A JavaScript egyedülálló abban, hogy végrehajt egy ilyen műveletet, és olvashatatlan karakterkészlettel zárul. Más nyelvekkel ellentétben, amelyek csak leállítják a programot.

Lehetségesek szomszédos architektúrák?

Jelenleg nincs olyan kapcsolódó technológia, amely egyszerre támogatná a statikus és dinamikus gépelést a programozási nyelvekben. És bátran kijelenthetjük, hogy nem fog megjelenni. Mivel az architektúrák alapvetően különböznek egymástól, és nem használhatók egyszerre.

Ennek ellenére bizonyos nyelveken további keretrendszerek segítségével módosíthatja a gépelést.

  • A Delphi programozási nyelvben a Variant alrendszer.
  • Az AliceML programozási nyelven - további csomagok.
  • A Haskell programozási nyelvben a Data.Dynamic könyvtár.

Mikor jobb az erős gépelés, mint a dinamikus gépelés?

Az erős gépelés előnyét a dinamikussal szemben egyértelműen csak akkor fogadhatja el, ha Ön kezdő programozó. Ebben abszolút minden IT-szakember egyetért. Az alapvető és alapvető programozási ismeretek tanításakor jobb, ha erős gépelést használunk, hogy némi fegyelmet szerezzünk a változókkal való munka során. Utána ha kell, át lehet váltani a dinamikára, de az erős gépeléssel elsajátított készségek fontos szerepet kapnak. Megtanulja, hogyan kell gondosan ellenőrizni a változókat, és figyelembe venni a típusukat a kód tervezése és írása során.

A dinamikus gépelés előnyei

  • Minimalizálja a karakterek és kódsorok számát a változók szükségtelen előzetes deklarálása és típusának megadása miatt. A típus automatikusan meghatározásra kerül az érték hozzárendelése után.
  • Kis kódblokkokban a konstrukciók vizuális és logikai érzékelése leegyszerűsödik az "extra" deklarációs sorok hiánya miatt.
  • A dinamika pozitív hatással van a fordító sebességére, mivel nem veszi figyelembe a típusokat, és nem ellenőrzi azok megfelelőségét.
  • Növeli a rugalmasságot és lehetővé teszi sokoldalú dizájn létrehozását. Ha például olyan metódust hoz létre, amelynek interakcióba kell lépnie egy adattömbbel, nem kell külön függvényeket létrehoznia a numerikus, szöveges és más típusú tömbök használatához. Elég egy metódust leírni, és minden típussal működni fog.
  • Leegyszerűsíti az adatbázis-kezelő rendszerekből származó adatok kiadását, így a dinamikus gépelést aktívan alkalmazzák a webalkalmazások fejlesztésében.

Tudjon meg többet a statikus gépelést használó programozási nyelvekről

  • A C++ a legszélesebb körben használt általános célú programozási nyelv. Ma már több jelentős kiadással és nagy felhasználói sereggel rendelkezik. Rugalmassága, korlátlan bővíthetősége és különféle programozási paradigmák támogatása miatt vált népszerűvé.

  • A Java egy olyan programozási nyelv, amely objektum-orientált megközelítést használ. Népszerűségre tett szert a többplatform miatt. Lefordításkor a kód bájtkódba kerül értelmezésre, amely bármely operációs rendszeren végrehajtható. A Java és a dinamikus gépelés nem kompatibilis, mert a nyelv erősen gépelt.

  • A Haskell az egyik legnépszerűbb nyelv, amelynek kódja integrálható más nyelvekbe, és kölcsönhatásba léphet más nyelvekkel. De a rugalmasság ellenére erős a gépelés. Nagy beépített típuskészlettel és saját készítésének lehetőségével.

Tudjon meg többet a dinamikus gépelést használó programozási nyelvekről

  • A Python egy programozási nyelv, amelyet elsősorban a programozó munkájának megkönnyítésére hoztak létre. Számos funkcionális fejlesztéssel rendelkezik, amelyeknek köszönhetően növeli a kód olvashatóságát és írását. Ezt sok szempontból a dinamikus gépelésnek köszönhetően sikerült elérni.

  • A PHP egy szkriptnyelv. Széles körben használják a webfejlesztésben, interakciót biztosítva adatbázisokkal interaktív dinamikus weboldalak létrehozásához. A dinamikus gépelésnek köszönhetően nagyban megkönnyíti az adatbázisokkal való munkát.

  • A JavaScript a fentebb már említett programozási nyelv, amely a webes technológiákban talált alkalmazást a kliens oldalon futó web szkriptek létrehozására. A dinamikus gépelés a kód írásának megkönnyítésére szolgál, mivel általában kis blokkokra van felosztva.

Dinamikus gépelés – Hátrányok

  • Ha a változók használata vagy deklarálása során gépelési hibát vagy baklövést követtek el, a fordító nem jeleníti meg. És problémák merülnek fel a program végrehajtása során.
  • Statikus gépelés esetén általában minden változó- és függvénydeklaráció bekerül külön fájl, amely lehetővé teszi, hogy a jövőben egyszerűen készítsen dokumentációt, vagy akár magát a fájlt használja dokumentációként. Ennek megfelelően a dinamikus gépelés nem teszi lehetővé ilyen funkció használatát.

Összesít

A statikus és dinamikus gépelést teljesen más célokra használják. Egyes esetekben a fejlesztők funkcionális előnyökre törekednek, más esetekben pedig pusztán személyes indítékokra. Mindenesetre annak érdekében, hogy saját maga határozza meg a gépelés típusát, alaposan tanulmányoznia kell azokat a gyakorlatban. A jövőben ez egy új projekt létrehozásakor és a hozzá tartozó gépelés kiválasztásakor nagy szerepet fog játszani, és megérteni fogja a hatékony választást.

Amikor programozási nyelveket tanul, gyakran hall olyan kifejezéseket, mint a „statikusan beírva” vagy a „dinamikusan beírva”. Ezek a fogalmak a típusellenőrzés folyamatát írják le, és mind a statikus típusellenőrzés, mind a dinamikus típusellenőrzés különböző típusrendszerekre vonatkozik. A típusrendszer olyan szabályok halmaza, amelyek egy "típusnak" nevezett tulajdonságot rendelnek a program különböző entitásaihoz: változókhoz, kifejezésekhez, függvényekhez vagy modulokhoz – azzal a végső céllal, hogy csökkentsék a hibákat az adatok helyes megjelenítésének biztosításával.

Ne aggódjon, tudom, hogy ez az egész zavaróan hangzik, ezért kezdjük az alapokkal. Mi az a „típusellenőrzés”, és mi a típus általában?

típus

A dinamikus típusellenőrzésen átmenő kód általában kevésbé optimalizált; ezen túlmenően fennáll a futásidejű hibák lehetősége, és ennek eredményeként minden futtatás előtt ellenőrizni kell. A dinamikus gépelés azonban utat nyit más hatékony programozási technikák, például a metaprogramozás előtt.

Gyakori tévhitek

1. tévhit: Statikus/dinamikus gépelés == erős/gyenge gépelés

Gyakori tévhit, hogy minden statikusan beírt nyelv erősen, a dinamikusan gépelt nyelv pedig gyengén van gépelve. Ez helytelen, és itt van az ok.

Az erősen tipizált nyelv az, amelyben a változók meghatározott adattípusokhoz vannak kötve, és amely típushibát ad, ha a várt és a tényleges típus nem egyezik az ellenőrzés során. A legegyszerűbb az erősen gépelt nyelvet erősen típusbiztonságos nyelvnek tekinteni. Például a fent már használt kódrészletben egy erősen gépelt nyelv kifejezett típushibát produkál, amely megszakítja a programot:

X = 1 + "2"

A statikusan beírt nyelveket, például a Java-t és a C#-t gyakran társítjuk erősen begépelt nyelvekkel (ezek azok), mert az adattípus kifejezetten a változó inicializálásakor van beállítva – mint ebben a Java-példában:

String foo = new String("hello world");

Ugyanakkor a Ruby, a Python és a JavaScript (mindegyik dinamikusan begépelve) is erősen begépelt, bár a fejlesztőnek nem kell megadnia a változó típusát a deklaráláskor. Tekintsük ugyanezt a példát, de Ruby nyelven írva:

Foo = "helló világ"

Mindkét nyelv erősen gépelt, de eltérő típus-ellenőrzési módszereket használ. Az olyan nyelvek, mint a Ruby, a Python és a JavaScript, nem igényelnek explicit típusdefiníciókat a típuskövetkeztetés miatt, amely képes programozottan következtetni a változó helyes típusára az értéke alapján. A típuskövetkeztetés a nyelv különálló tulajdonsága, és nem vonatkozik a típusrendszerekre.

A lazán tipizált nyelv olyan nyelv, amelyben a változók nincsenek egy adott adattípushoz kötve; típusuk még van, de a típusbiztonsági korlátozások sokkal gyengébbek. Tekintsük a következő PHP kód példát:

$foo = "x"; $foo = $foo + 2; // nem hiba echo $foo; 2

Mivel a PHP gyengén van beírva, ebben a kódban nincs hiba. Az előző javaslathoz hasonlóan nem minden gyengén tipizált nyelv van dinamikusan gépelve: a PHP egy dinamikusan tipizált nyelv, de a C, szintén gyengén tipizált nyelv, valóban statikusan van gépelve.

A mítosz megtört.

Bár statikus / dinamikus és erős / gyenge rendszer típusok és különbözőek, mindkettő a típusbiztonsággal kapcsolatos. A legegyszerűbb úgy fogalmazni, hogy az első rendszer megmondja, hogy mikor ellenőrizték a típusbiztonságot, a második pedig azt, hogy hogyan.

2. tévhit: Statikus/dinamikus gépelés == fordított/értelmezett nyelvek

Igaz, hogy a legtöbb statikusan tipizált nyelvet általában fordítják, és a dinamikusan gépelt nyelveket értelmezik, de ez az állítás nem általánosítható, és van erre egy egyszerű példa.

Amikor nyelvi tipizálásról beszélünk, akkor a nyelv egészéről beszélünk. Például nem mindegy, hogy a Java melyik verzióját használja – mindig statikusan lesz beírva. Ez eltér attól az esettől, amikor a nyelvet fordítják vagy értelmezik, mivel ebben az esetben a nyelv egy konkrét megvalósításáról beszélünk. Elméletileg bármely nyelv összeállítható és értelmezhető. A Java nyelv legnépszerűbb implementációja a bájtkódolási fordítást használja, amelyet a JVM értelmez – de ennek a nyelvnek vannak más olyan implementációi is, amelyek közvetlenül gépi kódba fordítanak, vagy úgy értelmezik, ahogy vannak.

Ha ez még mindig nem világos, azt tanácsolom, hogy olvassa el ezt a sorozatot.

Következtetés

Tudom, hogy sok információ volt ebben a cikkben – de azt hiszem, jól értetted. Külön cikkbe szeretném áthelyezni az erős/gyenge gépelésről szóló információkat, de ez nem olyan fontos téma; ráadásul be kellett mutatni, hogy ennek a fajta gépelésnek semmi köze a típusellenőrzéshez.

Nincs egyetlen válasz a „melyik gépelés a jobb?” kérdésre. - mindegyiknek megvannak a maga előnyei és hátrányai. Egyes nyelvek – például a Perl és a C# – lehetővé teszik, hogy saját maga válasszon statikus és dinamikus típusellenőrző rendszerek között. Ezeknek a rendszereknek a megértése lehetővé teszi a felmerülő hibák természetének jobb megértését, valamint megkönnyíti azok kezelését.



Betöltés...
Top