programozási stílus From Wikipedia, the free encyclopedia
A generikus programozás a számítógépes programozás egy olyan stílusa, amelyben az algoritmusok a később meghatározandó típusok formájában írják fel, amelyeket aztán szükség esetén megadnak a a paraméterként megadott típusokhoz. Ezt a megközelítést az ML programozási nyelv vezette be 1973[1]-ban,[2] lehetővé teszi olyan közös függvények vagy típusok írását, amelyek csak abban a típusban különböznek egymástól, amelyen működnek, így csökkentve a párhuzamosságot. Az ilyen szoftverek entitások néven generikus Ada, C #, Delphi, Eiffel, F #, Java, Nim, Python, Rust, Swift, TypeScript és a Visual Basic .NET. Paraméteres polimorfizmusként ismerik őket az ML-ben, Scalában, Juliában és Haskellben (a Haskell-közösség a "generikus" kifejezést is használja egy kapcsolódó, de némileg eltérő fogalomra); sablonok C++ és D nyelven; és paraméterezett típusok a nagy hatású 1994-es Design Patterns[3] című könyvben.
A "generikus programozás" kifejezést eredetileg David Musser és Alexander Stepanov találta ki a fentieknél specifikusabban, egy olyan programozási paradigma leírására, amelyben a típusokra vonatkozó alapvető követelményeket elvonták az algoritmusok és adatstruktúrák konkrét példáitól, és formalizálták mint fogalmak, általános funkciókkal, amelyek e fogalmak szempontjából valósulnak meg, jellemzően a fent leírt nyelvi genericitási mechanizmusokat alkalmazva.
Az általános programozást Musser & Stepanov (1989) a következőképpen határozza meg: A "generikus programozás" paradigma a szoftverbontás megközelítése, amelynek során a típusokra vonatkozó alapvető követelményeket az algoritmusok és az adatstruktúrák konkrét példáiból elvonják és fogalommá formálják, analóg módon az algebrai elméletek absztrakt algebrai absztrakciójával. Ennek a programozási megközelítésnek a korai példáit a Scheme és az Ada hajtották végre, bár a legismertebb példa a Standard Template Library (STL), amely kifejlesztette az iterátorok elméletét, amelyet a szekvencia adatstruktúrák és az azokon működő algoritmusok leválasztására használnak.
Például adott N szekvencia adatstruktúra, pl. egyenként összekapcsolt lista, vektor stb. és M algoritmusok a működésükhöz megkeresés, rendezés stb., közvetlen megközelítéssel minden algoritmust konkrétan az egyes adatstruktúrákra valósítanának meg, N × M kombinációkat adva a megvalósításhoz. A generikus programozási megközelítésben azonban az egyes adatstruktúrák egy iterátor koncepció modelljét adják vissza (egyszerű értéktípus, amelyre le lehet vonni az aktuális érték lekérésére, vagy megváltoztatható, hogy a sorozat másik értékére mutasson), és minden algoritmust megírnak általában az ilyen iterátorok érveivel, pl egy iterátor pár, amely a feldolgozásra kerülő rész vagy tartomány kezdetére és végére mutat. Így csak N + M adatstruktúra-algoritmus kombinációkra van szükség. Az STL több iterátor fogalmat határoz meg, amelyek mindegyike egy szűkebb fogalom finomítását pl. az előremenő iterátorok csak a következő értékre biztosítanak mozgást egy szekvenciában (pl. alkalmasak külön-külön összekapcsolt listához vagy bemeneti adatfolyamhoz), míg a véletlen hozzáférésű iterátor közvetlen állandó idejű hozzáférést biztosít a szekvencia bármely eleméhez (például vektorhoz). Fontos szempont, hogy egy adatstruktúra a legáltalánosabb, hatékonyan megvalósítható koncepció modelljét adja vissza - a számítási bonyolultsági követelmények kifejezetten a fogalommeghatározás részét képezik. Ez korlátozza azokat az adatstruktúrákat, amelyekre egy adott algoritmus alkalmazható, és az ilyen bonyolultsági követelmények döntően meghatározzák az adatstruktúra választását. Az generikus programozást hasonlóan alkalmazták más területeken is, pl. gráfalgoritmusokban.
Megjegyezzük, hogy bár ez a megközelítés gyakran használja a fordítási idő genericitásának / sablonjainak nyelvi jellemzőit, valójában független bizonyos nyelv-technikai részletektől. Alekszandr Sztepanov, a programozás úttörője írta:
„Generic programming is about abstracting and classifying algorithms and data structures. It gets its inspiration from Knuth and not from type theory. Its goal is the incremental construction of systematic catalogs of useful, efficient and abstract algorithms and data structures. Such an undertaking is still a dream.”
– Alexander Stepanov
„I believe that iterator theories are as central to Computer Science as theories of rings or Banach spaces are central to Mathematics.”
– Alexander Stepanov
Bjarne Stroustrup megjegyezte,
Egyéb programozási paradigmák, amelyeket generikus programozásnak neveznek, magukban foglalják a Datatype generikus programozást, a "Generic Programming – an Introduction" részben leírtak szerint. A Scrap your boilerplate megközelítés egy könnyű, általános programozási megközelítés a Haskell számára.[7]
Ebben a cikkben megkülönböztetjük a fenti általános programozás magas szintű programozási paradigmáit az azok végrehajtásához használt alacsonyabb szintű programozási nyelv genericitási mechanizmusaitól(lásd: Programozási nyelv támogatása az általánossághoz ). Az általános programozási paradigmák további tárgyalásához és összehasonlításához lásd.[8]
Az általános szolgáltatások magas szintű nyelvekben legalább az 1970-es évek óta léteznek olyan nyelveken, mint az ML, CLU és Ada, és ezt követően számos objektumalapú és objektumorientált nyelv átvette őket, köztük a BETA, C++, D, Eiffel, Java, és a DEC mára megszűnt Trellis-Owl nyelve.
A genericitást a különböző programozási nyelvek különbözőképpen valósítják meg és támogatják; a "generikus" kifejezést másképp használták a különféle programozási összefüggésekben is. Például a Forthban a fordító lefordíthat kódot fordítás közben, és új fordítói kulcsszavakat és megvalósításokat hozhat létre ezekhez a szavakhoz menet közben. Kevés olyan szó van, amely kiteszi a fordító viselkedését, ezért természetesen olyan genericitást kínál, amelyre azonban a legtöbb Forth szöveg nem hivatkozik. Hasonlóképpen, a dinamikusan tipizált nyelvek, különösen az értelmezett nyelvek, alapértelmezés szerint általában genericitást kínálnak, mivel az értékek átadása a funkcióknak és az értékadás hozzárendelésük típus-közömbös, és ezt a viselkedést gyakran használják absztrakció vagy kód szűkszavúsága szempontjából, azonban ez általában nem címkézett általános, a nyelv által alkalmazott dinamikus gépelési rendszer közvetlen következménye. A kifejezést a funkcionális programozásban használták, különösen a Haskell-szerű nyelvekben, amelyek olyan szerkezeti típusú rendszert használnak, ahol a típusok mindig paraméteresek, és az adott típusok tényleges kódja általános. Ezek a felhasználások továbbra is a kódmentés és az absztrakció renderelésének hasonló célját szolgálják.
A tömbök és a struktúrák előre definiált általános típusokként tekinthetők. A tömb vagy a struktúra minden használata egy új konkrét típust eredményez, vagy újból felhasznál egy korábbi példányt. A tömb és a struktúra elemtípusok paraméterezett típusok, amelyek a megfelelő általános típus példázására szolgálnak. Mindez általában beépül a fordítóba, és a szintaxis eltér a többi általános konstrukciótól. Néhány kibővíthető programozási nyelv megpróbálja egységesíteni a beépített és a felhasználó által meghatározott általános típusokat.
A programozási nyelvek genericitási mechanizmusainak átfogó felmérése következik. Egy specifikus felméréshez, amely összehasonlítja a mechanizmusok alkalmasságát az általános programozásra.
Ha a konténerosztályokat statikusan tipizált nyelveken hozza létre, kényelmetlen konkrét megvalósításokat írni az egyes tartalmazott adattípusokhoz, különösen, ha az egyes adattípusok kódja gyakorlatilag megegyezik. Például a C++ nyelven a kód duplikációja megkerülhető egy osztálysablon meghatározásával:
template<typename T>
class List {
// Class contents.
};
List<Animal> list_of_animals;
List<Car> list_of_cars;
Fent a T helyőrzője annak a típusnak, amelyet a lista létrehozásakor megadnak. Ezek a "T-típusú konténerek", amelyeket sablonoknak hívnak, lehetővé teszik egy osztály újra felhasználását különböző adattípusokkal, mindaddig, amíg bizonyos szerződéseket, például altípusokat és aláírást megtartanak. Ezt az általános jellegű mechanizmust nem szabad összetéveszteni az inklúziós polimorfizmussal, amely a cserélhető alosztályok algoritmikus használata: például egy Moving_Object típusú objektumok listája, amelyek Animal és Car típusú objektumokat tartalmaznak. A sablonok típusfüggetlen funkciókhoz is használhatók, mint az alábbi Swap példában:
// "&" denotes a reference
template<typename T>
void Swap(T& a, T& b) { // A similar, but safer and potentially faster function
// is defined in the standard library header <utility>
T temp = b;
b = a;
a = temp;
}
std::string world = "World!";
std::string hello = "Hello, ";
Swap(world, hello);
std::cout << world << hello << ‘\n’; // Output is "Hello, World!".
A fent használt C ++ sablonkonstrukciót széles körben idézik , mint azon generikus konstrukciót, amely népszerűsítette a fogalmat a programozók és nyelvtervezők körében, és számos általános programozási idiómát támogat. A D programozási nyelv a C ++ precedensen alapuló, de egyszerűsített szintaxissal teljesen generikus képes sablonokat is kínál. A Java programozási nyelv a J2SE 5.0 bevezetése óta szintaktikailag a C++-on alapuló generikus lehetőségeket kínál.
A C# 2.0, az Oxygene 1.5 (más néven Chrome) és a Visual Basic .NET 2005 olyan konstrukciókkal rendelkezik, amelyek kihasználják a Microsoft .NET-keretrendszerben a 2.0-ás verzió óta meglévő generikumok támogatását.
Az Adának már azóta is vannak generikus termékei, amióta 1977–1980-ig tervezték. A szabványos könyvtár általános szolgáltatásokat használ számos szolgáltatás nyújtásához. Az Ada 2005 egy átfogó, általános tároló könyvtárat egészít ki a standard könyvtárban, amelyet a C++ szabványos sablonkönyvtár ihletett.
Az általános egység olyan csomag vagy alprogram, amely egy vagy több általános formális paramétert vesz fel.
Egy általános formális paraméter egy érték, egy változó, egy konstans, egy típus, egy alprogram, vagy akár egy másik kijelölt generikus egység példánya. Általános formális típusok esetében a szintaxis különbséget tesz diszkrét, lebegőpontos, fixpontos, hozzáférési (mutató) típusok stb. Között. Egyes formális paraméterek alapértelmezett értékekkel rendelkezhetnek.
Egy általános egység példányosításához a programozó átadja az egyes formálisok tényleges paramétereit. Az általános példány ekkor ugyanúgy viselkedik, mint bármely más egység. Lehetséges generikus egységek futás közbeni példányosítása, például egy hurok belsejében.
Az általános csomag specifikációja:
generic
Max_Size : Natural; -- a generic formal value
type Element_Type is private; -- a generic formal type; accepts any nonlimited type
package Stacks is
type Size_Type is range 0 .. Max_Size;
type Stack is limited private;
procedure Create (S : out Stack;
Initial_Size : in Size_Type := Max_Size);
procedure Push (Into : in out Stack; Element : in Element_Type);
procedure Pop (From : in out Stack; Element : out Element_Type);
Overflow : exception;
Underflow : exception;
private
subtype Index_Type is Size_Type range 1 .. Max_Size;
type Vector is array (Index_Type range <>) of Element_Type;
type Stack (Allocated_Size : Size_Type := 0) is record
Top : Index_Type;
Storage : Vector (1 .. Allocated_Size);
end record;
end Stacks;
A generic csomag azonnali végrehajtása:
type Bookmark_Type is new Natural;
-- records a location in the text document we are editing
package Bookmark_Stacks is new Stacks (Max_Size => 20,
Element_Type => Bookmark_Type);
-- Allows the user to jump between recorded locations in a document
Generic csomag példányának használata:
type Document_Type is record
Contents : Ada.Strings.Unbounded.Unbounded_String;
Bookmarks : Bookmark_Stacks.Stack;
end record;
procedure Edit (Document_Name : in String) is
Document : Document_Type;
begin
-- Initialise the stack of bookmarks:
Bookmark_Stacks.Create (S => Document.Bookmarks, Initial_Size => 10);
-- Now, open the file Document_Name and read it in...
end Edit;
A nyelvi szintaxis lehetővé teszi az általános formális paraméterekre vonatkozó korlátozások pontos meghatározását. Például meghatározható, hogy egy általános formális típus csak egy moduláris típust fogad el ténylegesnek. Lehetséges az általános formai paraméterek közötti korlátozások kifejezése is; például:
generic
type Index_Type is (<>); -- must be a discrete type
type Element_Type is private; -- can be any nonlimited type
type Array_Type is array (Index_Type range <>) of Element_Type;
Ebben a példában az Array_Type-t mind az Index_Type, mind az Element_Type korlátozza. Az egység példányosításakor a programozónak át kell adnia egy tényleges tömbtípust, amely kielégíti ezeket a korlátozásokat.
Ennek a finomszemcsés vezérlésnek a hátránya egy bonyolult szintaxis, de mivel az összes általános formális paraméter teljesen meghatározva van a specifikációban, a fordító képes példányokat generálni anélkül, hogy megnézné az általános törzsét.
A C++-tól eltérően az Ada nem engedélyezi a speciális generikus példányokat, és megköveteli, hogy minden generikust kifejezetten példányosítsanak. Ezeknek a szabályoknak számos következménye van:
A C ++ sablonokkal engedélyezi az általános programozási technikákat. A C++ Standard Library tartalmazza a Standard Template Libraryt (STL), amely sablonok kereteit biztosítja a közös adatstruktúrákhoz és algoritmusokhoz. A C ++ formátumú sablonok szintén használhatók a sablon metaprogramozásához, amely a kód egy részének előre történő értékelése a fordítás idején, nem pedig a futás idején. A sablonspecializáció segítségével a C ++ sablonokat befejezettnek tekintjük.
Sokféle sablon létezik: függvénysablonok, osztálysablonok ... A függvénysablon a szokásos függvények létrehozásának mintája a példányosításkor megadott paraméterezési típusok alapján. Például a C++ Standard Template Library tartalmazza a max (x, y) függvénysablont, amely olyan funkciókat hoz létre, amelyek x vagy y értéket adnak vissza, attól függően, hogy melyik nagyobb. A max() így definiálható:
template<typename T>
T max(T x, T y) {
return x < y ? y : x;
}
Ennek a függvénysablonnak a specializációit, a konkrét típusokkal való példányosításokat ugyanúgy lehet hívni, mint egy közönséges függvényt:
std::cout << max(3, 7); // Outputs 7.
A fordító megvizsgálja a max hívásához használt argumentumokat, és megállapítja, hogy ez a max (int, int) hívása. Ezután példányosítja a függvény egy változatát, ahol a T típusú paraméterezés int, ezzel egyenértékűvé téve a következő függvényt:
int max(int x, int y) {
return x < y ? y : x;
}
Ez akkor működik, hogy az x és y argumentumok egész számok, karakterláncok vagy bármilyen más típusok, amelyekre az x <y kifejezés értelmes, vagy pontosabban bármely olyan típusra, amelyre az < operátor definiálva van. A használható típuskészlethez nincs szükség közös öröklődésre, ezért nagyon hasonlít a kacsa típusozásra. Egy egyedi adattípust meghatározó program az operátor túltöltésével meghatározhatja a < jelentését az adott típushoz, ezáltal lehetővé téve annak használatát a max () függvénysablonnal. Bár ez az elkülönített példában csekély előnynek tűnhet, egy olyan átfogó könyvtár összefüggésében, mint az STL, lehetővé teszi a programozó számára, hogy széles körű funkcionalitást szerezzen egy új adattípushoz, csupán néhány operátor meghatározásával. A < pusztán definiálása lehetővé teszi egy típus használatát a sort(), stable_sort(), and binary_search() algoritmusokkal, vagy olyan adatstruktúrákba, mint halmazok, kupacok és asszociatív tömbökbe helyezhető.
A C++ sablonok fordításkor teljesen típusbiztosak. Bemutatásként a standard típusú komplex nem definiálja az < operátort, mert a komplex számokon nincs szigorú sorrend. Ezért max (x, y) fordítási hibával meghiúsul, ha x és y komplex értékek. Hasonlóképpen, a (z) < -ra támaszkodó más sablonok nem alkalmazhatók összetett adatokra, hacsak nem biztosítunk összehasonlítást (funkció vagy funkció formájában). Például: Egy komplexum nem használható kulcsként egy térképhez, hacsak nincs összehasonlítás. Sajnos a fordítók történelmileg kissé ezoterikus, hosszú és haszontalan hibaüzeneteket generálnak az ilyen típusú hibákhoz. Annak biztosítása, hogy egy bizonyos objektum betartsa a metódusprotokollt, enyhítheti ezt a problémát. Azok a nyelvek, amelyek <helyett az összehasonlítást használják, összetett értékeket is használhatnak kulcsként.
Egy másik típusú sablon, egy osztálysablon, ugyanazt a koncepciót kiterjeszti az osztályokra is. Az osztálysablon specializáció egy osztály. Az osztálysablonokat gyakran használják általános tárolók készítéséhez. Például az STL-nek van egy összekapcsolt listatárolója. Az egész számok összekapcsolt listájának elkészítéséhez írja az list <int>-et. A karaktersorozatok listája a list <string>. A listához tartozik egy sor szabványos funkció, amelyek bármilyen kompatibilis paraméterezési típushoz használhatók.
A C ++ sablonjainak egyik fontos jellemzője a sablonok specializációja. Ez lehetővé teszi alternatív megvalósítások biztosítását a pillanatnyilag paraméterezett típus bizonyos jellemzői alapján. A sablon-specializációnak két célja van: az optimalizálás bizonyos formáinak lehetővé tétele és a kódduzzadás csökkentése.
Vegyünk például egy sort () sablonfüggvényt. Az egyik elsődleges tevékenység, amelyet egy ilyen funkció végez, az értékek cseréje vagy cseréje a konténer két pozíciójában. Ha az értékek nagyok (az egyes tárolásához szükséges bájtok számát tekintve), akkor gyakran gyorsabb, ha először külön mutató listát készítünk az objektumokra, rendezzük ezeket a mutatókat, majd elkészítjük a végső rendezett sorrendet. Ha az értékek meglehetősen kicsik, akkor általában a leggyorsabb az értékeket a helyükön cserélni, ha szükséges. Továbbá, ha a paraméterezett típus már valamilyen mutató típusú, akkor nincs szükség külön mutató tömb építésére. A sablon-specializáció lehetővé teszi a sablonkészítő számára, hogy különböző megvalósításokat írjon, és meghatározza azokat a jellemzőket, amelyekkel a paraméterezett típus/(ok)-nak rendelkezniük kell az egyes megvalósításokhoz.
A függvénysablonokkal ellentétben az osztálysablonok részben specializálódhatnak. Ez azt jelenti, hogy az osztály sablonkódjának alternatív változata akkor nyújtható, ha a sablon néhány paramétere ismert, míg a többi sablon paraméter általános marad. Ezt fel lehet használni például egy alapértelmezett megvalósítás (az elsődleges specializáció) létrehozására, amely feltételezi, hogy egy paraméterező típus másolása drága, majd részleges specializációkat hozhat létre olyan típusok számára, amelyek olcsón másolhatók, ezáltal növelve az általános hatékonyságot. Az ilyen osztálysablon kliensei csak annak szakterületeit használják, anélkül, hogy tudnunk kellene, hogy a fordító minden esetben használta-e az elsődleges vagy részleges specializációt. Az osztálysablonok teljes mértékben specializálódhatnak is, ami azt jelenti, hogy alternatív megvalósítás biztosítható, ha az összes paraméterező típus ismert.
A sablonok egyes felhasználásait, például a max () függvényt, korábban függvényszerű előprocesszor makrók töltötték ki (a C programozási nyelv öröksége). Például itt van egy ilyen makró lehetséges megvalósítása:
#define max(a,b) ((a) < (b) ? (b) : (a))
A makrókat az előprocesszor kibővíti (a másolatot beilleszti) a megfelelő fordítás előtt; a sablonok tényleges valós funkciók. A makrók mindig kibővítettek sorban; a sablonok akkor is helyben kifejtett függvények lehetnek, ha a fordító megfelelőnek tartja.
A sablonokat azonban általában a makrókkal szembeni javulásnak tekintik e célból. A sablonok típusbiztonságosak. A sablonok elkerülik a kódban található olyan gyakori hibákat, amelyek nagyban kihasználják a funkciószerű makrókat, például kétszer értékelik a mellékhatásokkal járó paramétereket. Talán a legfontosabb, hogy a sablonokat úgy tervezték, hogy sokkal nagyobb problémákra alkalmazhatók legyenek, mint a makrók.
A sablonok használatának négy elsődleges hátránya van: támogatott szolgáltatások, a fordító támogatása, gyenge hibaüzenetek (általában pre C++ 20 SFINAE esetén) és a kódfelfúvódása:
A sablonok által generált extra példányok bizonyos hibakeresőket is nehézségekbe ütközhetnek a sablonokkal való kecsesen. Például egy hibakeresési töréspont beállítása egy sablonon egy forrásfájlból elmulaszthatja a töréspont beállítását a kívánt aktuális példányosításban, vagy beállíthat egy töréspontot a sablon minden egyes példányában.
Ezenkívül a sablon megvalósításának forráskódjának teljes mértékben elérhetőnek kell lennie (pl. Egy fejlécben) az azt használó fordítóegység (forrásfájl) számára. Ha a fejlécfájlokban nem szerepelnek, a sablonok, beleértve a Standard könyvtár nagy részét, nem állíthatók össze. (Ez ellentétben áll a nem sablonos kódokkal, amelyeket binárisan lehet fordítani, és csak egy deklarációk fejlécfájlt adunk meg a kódot használva.) Ez hátrányt jelenthet a megvalósító kód feltárásával, amely eltávolít néhány kivonatot, és korlátozhatja annak felhasználás zárt forráskódú projektekben.
A D (programozási nyelv) támogatja a C++-on alapuló sablonokat. A legtöbb C++ sablon-idióma változások nélkül átkerül a D-be, de D további funkciókat ad hozzá:
A D-ben lévő sablonok más szintaxist használnak, mint a C++-nál: míg a C++-ban a sablonok szögletes zárójelbe vannak csomagolva (Template<param1, param2>), addig a D felkiáltójelet és zárójelet használ: Template! (Param1, param2). Ez elkerüli a C++ elemzési nehézségeket, mivel az összehasonlító operátorokkal kapcsolatos kétértelműség adódik. Ha csak egy paraméter van, akkor a zárójeleket el lehet hagyni.
Hagyományosan a D egyesíti a fenti jellemzőket, hogy tulajdonság-alapú általános programozással fordítási idejű polimorfizmust biztosítson. Például egy bemeneti tartomány minden olyan típusként definiálható, amely kielégíti az isInputRange által elvégzett ellenőrzéseket, amelyet a következőképpen definiálnak:
template isInputRange(R)
{
enum bool isInputRange = is(typeof(
(inout int = 0)
{
R r = R.init; // can define a range object
if (r.empty) {} // can test for empty
r.popFront(); // can invoke popFront()
auto h = r.front; // can get the front of the range
}));
}
Csak egy bemeneti tartományt elfogadó függvény használhatja a fenti sablont egy sablon-kényszerben:
auto fun(Range)(Range range)
if (isInputRange!Range)
{
// ...
}
A sablon metaprogramozása mellett a D számos funkciót is kínál a fordítási idő kódgenerálásának engedélyezéséhez:
A fentiek kombinálása lehetővé teszi a kód generálását a meglévő deklarációk alapján. Például a D sorosítási keretek felsorolhatják a típus tagjait, és minden sorosított típushoz speciális funkciókat generálhatnak a sorosítás és a deserializáció elvégzéséhez. A felhasználó által definiált attribútumok tovább jelezhetik a sorosítási szabályokat.
Az import kifejezés és a fordítási idő függvény végrehajtása szintén lehetővé teszi a tartományspecifikus nyelvek hatékony megvalósítását. Például egy olyan függvénynél, amely egy HTML sablont tartalmazó karaktersorozatot vesz fel, és egyenértékű D forráskódot ad vissza, akkor a következő módon lehet használni:
// Import the contents of example.htt as a string manifest constant.
enum htmlTemplate = import("example.htt");
// Transpile the HTML template to D code.
enum htmlDCode = htmlTemplateToD(htmlTemplate);
// Paste the contents of htmlDCode as D code.
mixin(htmlDCode);
Az általános osztályok az eredeti metódus és nyelvtervezés óta az Eiffel részét képezik. Az Eiffel alapítványi publikációi a genericitás kifejezést használják az általános osztályok létrehozásának és használatának leírására.
Az általános osztályokat osztálynevükkel és egy vagy több hivatalos általános paraméter listájával deklaráljuk. A következő kódban a LIST osztálynak van egy formális általános G paramétere
class
LIST [G]
...
feature -- Access
item: G
-- The item currently pointed to by cursor
...
feature -- Element change
put (new_item: G)
-- Add `new_item' at the end of the list
...
A formális általános paraméterek tetszőleges osztálynevek helyőrzői, amelyeket a generikus osztály deklarálásakor kell megadni, amint az az alábbi két általános származtatásban látható, ahol az ACCOUNT és DEPOSIT más osztálynév. Az ACCOUNT és a DEPOSIT tényleges általános paramétereknek számítanak, mivel valós osztályneveket adnak a G helyettesítésére a tényleges használat során.
list_of_accounts: LIST [ACCOUNT]
-- Account list
list_of_deposits: LIST [DEPOSIT]
-- Deposit list
Az Eiffel típusú rendszeren belül, bár a LIST [G] osztály osztálynak számít, nem tekinthető típusnak. A LIST [G] általános levezetése, például a LIST [ACCOUNT] azonban típusnak tekinthető.
A fenti listás osztály esetében a G -t helyettesítő tényleges általános paraméter bármely más rendelkezésre álló osztály lehet. Annak az osztályhalmaznak a korlátozására, amelyből érvényes tényleges általános paraméterek választhatók, meg lehet adni egy általános korlátozást. Az alábbi SORTED_LIST osztály deklarációban az általános megkötés előírja, hogy bármely érvényes tényleges általános paraméter olyan osztály lesz, amely a
COMPARABLE osztályból öröklődik. Az általános megkötés biztosítja, hogy a SORTED_LIST elemei valóban rendezhetők legyenek.
class
SORTED_LIST [G -> COMPARABLE]
2004-ben a J2SE 5.0 részeként hozzáadták a Java programozási nyelvhez az általános vagy a "T-type-type-type" támogatást. A Java-ban a generikusokat csak fordításkor ellenőrizzük a típushelyesség szempontjából. A generikus típusú információkat ezután a törlés nevű folyamaton keresztül eltávolítják, hogy fenntartsák a kompatibilitást a régi JVM-implementációkkal, ami futás közben nem érhető el. Például a List <String> konvertálódik a nyers List listává. A fordító beszúrja a típuskényszerítést, hogy az elemeket String típusúvá alakítsa, amikor lekerülnek a listáról, csökkentve a teljesítményt más megvalósításokhoz, például a C ++ sablonokhoz képest.
A generikus gyógyszereket a .NET Framework 2.0 részeként adták hozzá 2005 novemberében, a Microsoft Research 1999-ben elindított kutatási prototípusán alapulva. Noha a Java-generikusokhoz hasonlóan, a .NET-generikusok sem alkalmazzák a típusok törlését, hanem az általánosítást első osztályú mechanizmusként valósítják meg a futás során a reifikálás segítségével. Ez a tervezési lehetőség további funkcionalitást kínál, például lehetővé teszi a reflexiót az általános típusok megőrzésével, valamint enyhíti a törlés néhány korlátozását (például nem képes generikus tömbök létrehozására). Ez azt is jelenti, hogy a futásidők és a normálisan drága doboz-átalakítások nem érnek el teljesítményt. Ha primitív és értéktípusokat használnak általános érvekként, akkor speciális megvalósításokat kapnak, lehetővé téve a hatékony általános gyűjteményeket és metóduseket. Csakúgy, mint a C ++ és a Java esetében, a beágyazott általános típusok, mint például a Szótár <string, Lista <int>>, érvényesek, azonban a kódanalízis tervezési szabályaiban a tagok aláírása esetén nem ajánlott.
A .NET a generikus típusú korlátozások hat változatát teszi lehetővé, ahol a kulcsszó, ideértve az általános típusok korlátozását is, hogy értéktípusok, osztályok legyenek, konstruktorokkal rendelkezzenek és interfészeket valósítsanak meg. Az alábbiakban egy példa egy interfész-korlátozással:
using System;
class Sample
{
static void Main()
{
int[] array = { 0, 1, 2, 3 };
MakeAtLeast<int>(array, 2); // Change array to { 2, 2, 2, 3 }
foreach (int i in array)
Console.WriteLine(i); // Print results.
Console.ReadKey(true);
}
static void MakeAtLeast<T>(T[] list, T lowest) where T : IComparable<T>
{
for (int i = 0; i < list.Length; i++)
if (list[i].CompareTo(lowest) < 0)
list[i] = lowest;
}
}
MakeAtLeast () metódus lehetővé teszi a tömbökön történő műveletet, általános T. típusú elemekkel. A metódus típuskorlátozása azt jelzi, hogy a metódus bármely olyan T típusra alkalmazható, amely megvalósítja az általános IComparable <T> interfészt. Ez biztosítja a fordítási idő hibáját, ha a metódust meghívják, ha a típus nem támogatja az összehasonlítást. Az interfész biztosítja a CompTo (T) általános metódust.
A fenti metódus írható általános típusok nélkül is, egyszerűen a nem általános Array típust használva. Mivel azonban a tömbök ellentmondásosak, az öntés nem lenne biztonságos a típus szempontjából, és a fordító nem találna bizonyos lehetséges hibákat, amelyeket egyébként elkapnának az általános típusok használata során. Ezenkívül a metódusnak objektumokként kellene elérnie a tömb elemeit, és két elem összehasonlításához castingra lenne szükség. (Az értéktípusokhoz, például az int típusokhoz, ez doboz-átalakítást igényel, bár ezt meg lehet
oldani a Comparer <T> osztály használatával, ahogy az a szokásos gyűjtési osztályokban történik.)
A statikus tagok figyelemre méltó viselkedése egy általános .NET osztályban a statikus tagok példányosítása futási időnként (lásd az alábbi példát).
//A generic class
public class GenTest<T>
{
//A static variable - will be created for each type on reflection
static CountedInstances OnePerType = new CountedInstances();
//a data member
private T mT;
//simple constructor
public GenTest(T pT)
{
mT = pT;
}
}
//a class
public class CountedInstances
{
//Static variable - this will be incremented once per instance
public static int Counter;
//simple constructor
public CountedInstances()
{
//increase counter by one during object instantiation
CountedInstances.Counter++;
}
}
//main code entry point
//at the end of execution, CountedInstances.Counter = 2
GenTest<int> g1 = new GenTest<int>(1);
GenTest<int> g11 = new GenTest<int>(11);
GenTest<int> g111 = new GenTest<int>(111);
GenTest<double> g2 = new GenTest<double>(1.0);
A Delphi Object Pascal dialektus a Delphi 2007 kiadásban szerzett generikus anyagokat, kezdetben csak a (most már megszűnt).NET fordítóval, mielőtt hozzáadták volna a natív kódhoz a Delphi 2009 kiadásban. A Delphi generikusok szemantikája és képességei nagyrészt azokra a mintákra épülnek, amelyeket a .NET 2.0-ban a generikusok rendelkeztek, bár a megvalósítás szükségszerűen egészen más. Itt van egy nagyjából közvetlen fordítás a fenti C # első példáról:
program Sample;
{$APPTYPE CONSOLE}
uses
Generics.Defaults; //for IComparer<>
type
TUtils = class
class procedure MakeAtLeast<T>(Arr: TArray<T>; const Lowest: T;
Comparer: IComparer<T>); overload;
class procedure MakeAtLeast<T>(Arr: TArray<T>; const Lowest: T); overload;
end;
class procedure TUtils.MakeAtLeast<T>(Arr: TArray<T>; const Lowest: T;
Comparer: IComparer<T>);
var
I: Integer;
begin
if Comparer = nil then Comparer := TComparer<T>.Default;
for I := Low(Arr) to High(Arr) do
if Comparer.Compare(Arr[I], Lowest) < 0 then
Arr[I] := Lowest;
end;
class procedure TUtils.MakeAtLeast<T>(Arr: TArray<T>; const Lowest: T);
begin
MakeAtLeast<T>(Arr, Lowest, nil);
end;
var
Ints: TArray<Integer>;
Value: Integer;
begin
Ints := TArray<Integer>.Create(0, 1, 2, 3);
TUtils.MakeAtLeast<Integer>(Ints, 2);
for Value in Ints do
WriteLn(Value);
ReadLn;
end.
A C # -hoz hasonlóan a metódusoknak és az egész típusoknak is lehet egy vagy több típusparaméterük. A példában a TArray egy általános típus (amelyet a nyelv határoz meg), a MakeAtLeast pedig egy általános metódus. Az elérhető korlátozások nagyon hasonlítanak a C # -ben elérhető korlátozásokhoz: bármilyen értéktípus, bármely osztály, egy adott osztály vagy interfész és egy paraméter nélküli konstruktorral rendelkező osztály. A többféle kényszer additív unióként működik.
A Free Pascal generikus alkalmazásokat hajtott végre a Delphi előtt, különböző szintaxissal és szemantikával. Az FPC 2.6.0 verziója óta azonban a Delphi stílusú szintaxis elérhető a {$ mode Delphi} nyelvi mód használatakor. Így a Free Pascal programozói képesek generikusokat használni, bármelyik stílusban, amelyet jobban szeretnek.
Delphi és Free Pascal példa:
// Delphi style
unit A;
{$ifdef fpc}
{$mode delphi}
{$endif}
interface
type
TGenericClass<T> = class
function Foo(const AValue: T): T;
end;
implementation
function TGenericClass<T>.Foo(const AValue: T): T;
begin
Result := AValue + AValue;
end;
end.
// Free Pascal's ObjFPC style
unit B;
{$ifdef fpc}
{$mode objfpc}
{$endif}
interface
type
generic TGenericClass<T> = class
function Foo(const AValue: T): T;
end;
implementation
function TGenericClass.Foo(const AValue: T): T;
begin
Result := AValue + AValue;
end;
end.
// example usage, Delphi style
program TestGenDelphi;
{$ifdef fpc}
{$mode delphi}
{$endif}
uses
A,B;
var
GC1: A.TGenericClass<Integer>;
GC2: B.TGenericClass<String>;
begin
GC1 := A.TGenericClass<Integer>.Create;
GC2 := B.TGenericClass<String>.Create;
WriteLn(GC1.Foo(100)); // 200
WriteLn(GC2.Foo('hello')); // hellohello
GC1.Free;
GC2.Free;
end.
// example usage, ObjFPC style
program TestGenDelphi;
{$ifdef fpc}
{$mode objfpc}
{$endif}
uses
A,B;
// required in ObjFPC
type
TAGenericClassInt = specialize A.TGenericClass<Integer>;
TBGenericClassString = specialize B.TGenericClass<String>;
var
GC1: TAGenericClassInt;
GC2: TBGenericClassString;
begin
GC1 := TAGenericClassInt.Create;
GC2 := TBGenericClassString.Create;
WriteLn(GC1.Foo(100)); // 200
WriteLn(GC2.Foo('hello')); // hellohello
GC1.Free;
GC2.Free;
end.
A Haskell típusosztály-mechanizmusa támogatja az általános programozást. A Haskell előre definiált típusosztályai közül hatnak (beleértve az Eq-t, az egyenlőség szempontjából összehasonlítható típusokat, és a Show-t, amelyeknek az értékei karakterláncként ábrázolhatók) különleges tulajdonsága a származtatott példányok támogatása. Ez azt jelenti, hogy egy új típust definiáló programozó kijelentheti, hogy ennek a típusnak ezen speciális típusú osztályok egyikének kell lennie, anélkül, hogy az osztály metódusainak megvalósítását biztosítaná, amire az osztálypéldányok deklarálásakor általában szükség van. Az összes szükséges metódust "kivezetik" - vagyis automatikusan elkészítik - a típus szerkezete alapján. Például egy bináris fafajta következő deklarációja kimondja, hogy az Eq és Show osztályok példányának kell lennie:
data BinTree a = Leaf a | Node (BinTree a) a (BinTree a)
deriving (Eq, Show)
Ez azt eredményezi, hogy az egyenlőségfüggvény (==) és a karakterlánc-reprezentációs függvény (show) automatikusan definiálásra kerül a BinTree T űrlap bármely típusához, feltéve, hogy T maga is támogatja ezeket a műveleteket.
Az Eq és a Show származtatott példányainak támogatása metóduseik == -ig teszik lehetővé, és a para-metrikusan polimorf függvényektől minőségileg eltérő módon mutatják az általános képeket: ezek a "függvények" (pontosabban típus-indexelt függvénycsaládok) alkalmazhatók a különféle típusok, és bár minden argumentumtípusonként eltérő módon viselkednek, kevés munka szükséges egy új típus támogatásához. Ralf Hinze (2004) kimutatta, hogy a felhasználó által definiált típusú osztályokhoz hasonló hatás érhető el bizonyos programozási technikákkal. Más kutatók ennek és másfajta nagylelkűségnek a megközelítését javasolták a Haskell és a Haskell kiterjesztéseivel összefüggésben (az alábbiakban tárgyaljuk).
A PolyP volt az első általános programozási nyelv kiterjesztés a Haskellhez. A PolyP-ben az általános függvényeket politipikusnak nevezzük. A nyelv egy speciális konstrukciót vezet be, amelyben az ilyen politípusos funkciók strukturális indukcióval definiálhatók egy szabályos adattípus mintafunkciójának struktúráján. A PolyP rendszeres adattípusai a Haskell adattípusok részhalmaza. A t reguláris adattípusnak * → * típusúnak kell lennie, és ha a definícióban az a formális típusú argumentum, akkor minden t-re irányuló rekurzív hívásnak t a formájúnak kell lennie. Ezek a korlátozások kizárják a magasabb típusú adattípusokat, valamint a beágyazott adattípusokat, ahol a rekurzív hívások más formájúak. A PolyP lapítás funkciója itt példaként szolgál:
flatten :: Regular d => d a -> [a]
flatten = cata fl
polytypic fl :: f a [a] -> [a]
case f of
g+h -> either fl fl
g*h -> \(x,y) -> fl x ++ fl y
() -> \x -> []
Par -> \x -> [x]
Rec -> \x -> x
d@g -> concat . flatten . pmap fl
Con t -> \x -> []
cata :: Regular d => (FunctorOf d a b -> b) -> d a -> b
A Generic Haskell a Haskell újabb kiterjesztése, amelyet a hollandiai Utrechti Egyetemen fejlesztettek ki. Az általa nyújtott kiterjesztések a következők:
Az így kapott indexelt érték bármilyen típusra specializálható.
Például az egyenlőség függvénye a Generic Haskellben:
type Eq {[ * ]} t1 t2 = t1 -> t2 -> Bool
type Eq {[ k -> l ]} t1 t2 = forall u1 u2. Eq {[ k ]} u1 u2 -> Eq {[ l ]} (t1 u1) (t2 u2)
eq {| t :: k |} :: Eq {[ k ]} t t
eq {| Unit |} _ _ = True
eq {| :+: |} eqA eqB (Inl a1) (Inl a2) = eqA a1 a2
eq {| :+: |} eqA eqB (Inr b1) (Inr b2) = eqB b1 b2
eq {| :+: |} eqA eqB _ _ = False
eq {| :*: |} eqA eqB (a1 :*: b1) (a2 :*: b2) = eqA a1 a2 && eqB b1 b2
eq {| Int |} = (==)
eq {| Char |} = (==)
eq {| Bool |} = (==)
A Clean általános programozási alapú PolyP-t és az általános Haskell-t kínálja, amelyet a GHC> = 6.0 támogat. Ez természetük szerint paraméterez, de túlterhelést kínál.
Az ML család nyelvei támogatják az általános programozást a parametrikus polimorfizmus és az úgynevezett functor modulok révén. A Standard ML és az OCaml egyaránt biztosít funkciókat, amelyek hasonlóak az osztálysablonokhoz és Ada általános csomagjaihoz. A rendszer szintaktikai absztrakciói szintén kapcsolódnak a generikussághoz - ezek valójában a C ++ sablonok egy halmaza.
A Verilog modul tartalmazhat egy vagy több paramétert, amelyekhez a modul példányosításakor hozzárendelik tényleges értékeiket. Az egyik példa egy általános regiszter tömb, ahol a tömb szélességét egy paraméter adja meg. Egy ilyen tömb általános vezetékvektorral kombinálva tetszőleges bitszélességű általános puffert vagy memóriamodult készíthet egyetlen modul megvalósításából.
Az Adától származó VHDL általános képességekkel is rendelkezik.
#define cbrt(x) _Generic((x), long double: cbrtl, \
default: cbrt, \
float: cbrtf)(x)
Seamless Wikipedia browsing. On steroids.
Every time you click a link to Wikipedia, Wiktionary or Wikiquote in your browser's search results, it will show the modern Wikiwand interface.
Wikiwand extension is a five stars, simple, with minimum permission required to keep your browsing private, safe and transparent.