Je hezké jak se novější nízkoúrovňové jazyky liší v podpoře metaprogramování. Každý si vybere:
Na rozdíl od Zigu však makra v C3 dovedou brát celý blok kódu, což mi přijde jako velice užitečná schopnost. Např. makro @pool, které si před spuštěním bloku zapamatuje stav alokátoru pro dočasnou paměť a po dokončení bloku obnoví zapamatovaný stav - tj. uvolní dočasnou paměť, která se v bloku používala:
fn void some_function()
{
@pool()
{
do_calculations();
};
// All temporary allocations inside of do_calculations
// and deeper down is freed when exiting the `@pool` scope.
}
Inline funkcie, generika.
No v tych modrnejsich jazykoch maju marka skor inu ulohu - compile time refelxia, proceduralne makra (Rust), source generatori (C#)
Tohle jsou krásné příklady použití maker. Jasně že některé věci dělané často makry se občas dají dělat i jinak. Ale já se ptal v kontextu toho, že by měly být antipattern.
BTW náhrada maker inline funkcemi má svá úskalí. Například debug buildy inlinují velice nerady. Stačí když nahradím nějaká titěrné makro, co vlastně nic nedělá, ale používá se na hromadě míst inline funkcí. Při ladění mě může čekat překvapení, jak je to najednou příšerně pomalé. A krokování přes takové nicnedělající mezikroky je občas taky otravné.
Právě makra v C3 nejsou jako makra v C. S těmi v C3 právě můžete dělat třeba tu compile-time reflexi.
Možno by stálo za to zamyslieť sa nad tým, či má viac zmysel články písať na základe názvu konštrukcie podporovanej jazykom alebo na základe funkcionalít, ktoré tieto konštrukcie poskytujú. Ja osobne v tom úplne jasno nemám, ale dáva mi asi väčší zmysel to druhé.
Pretože inak sa môže stať, niečo na spôsob toho, čo tu niekto nedávno písal, že C++ nepodporuje rozhrania, pritom ich podporuje úplne od začiatku, iba sa tak nevolajú.
Makrá v C alebo v C++ sa síce volajú rovnako ako makrá v C3, ale majú úplne iný účel a aj ich definícia v špecifikácii jednotlivých jazykov je na iných miestach.
Keby sme mali ísť do detailov, tak by vôbec nebolo prehnané tvrdenie, že makrá chápané ako makrá v C alebo v C++ sú súčasťou samostatného aj keď integrovaného jazyka, ktorý má nezávislú gramatiku, a samotné C alebo C++ začína až keď je vyhodnocovanie jazyka makier ukončené.
Takže vlastne dochádza k paradoxu, že je porovnávané makro ako jazyková konštrukcia z jazyka C, ktorá pritom ale v ani v C ani v C++ ako takom vlastne vôbec nie je, aj keď všeobecne môže byť chápané, že je.
Na rozdiel od toho, makrá c3 sú súčasťou c3 ako takého.
Čo sa porovnania funkcionalít týka, tak to, čo poskytujú makrá C3 v C++ do istej miery poskytuje kombinácia šablónového metaprogramovania, programovania za prekladu s constexpr a consteval a čiastočne statická reflexia z C++26, v rámci toho, čo je schválené, ktorá zatiaľ funguje v experimentálnych prekladačoch a ešte síce nedokáže modifikovať kód, ale dokáže ho vypísať v rámci jedného kroku zostavenia a použiť výstup v dalšom kroku zostavenia.
no na článek s porovnáním makrosystémů různých jazyků si teď už netroufám, protože to je hodně (HODNĚ) velké téma. V dnešním článku byl popis makrosystému jazyka C3 a to, jak se liší od maker v C/C++, ne porovnání toho, co C3 umí v porovnání s C/C++ (protože C3 je "vylepšením" C, takže programátory asi bude zajímat, jak se jim zjednodušil život oproti preprocesoru céčka).
Ale jestli je fakt požadavek na porovnání makrosystémů, no tam bych asi začal CLISPem, všechno další mi připadne horší :)
Nie nie, ja tu naozaj komunitu nereprezentujem, určite nie som vhodný indikátor záujmu a vôbec by ma nenapadlo, že by moje príspevky mali ovlyvňovať publikačnú činnosť najvyťaženejšieho autora na týchto stránkach.
A potrebu ukázať ľuďom, ktorí majú nejaký kód v C ako sa dá previesť do C3 celkom chápem.
já se obecně tomu článku nebráním, ale to téma maker je skutečně hodně široké. Na jedné straně C makra (=textové substituce), na straně druhé možnost manipulovat s AST nebo klidně i s typovým systémem. A mezi tím ty "normální" jazyky :-)
To je práve to. Že je asi nutné to veľmi viditeľne rozdeliť na úrovne. Ktoré sú naozaj, teda pokiaľ som niečo neprehliadol, tri: textová, syntaktická a sémantická.
Pretože akonáhle sa niečo volá makro, tak si to niekto okamžite spojí s makrami v C, potom s tým, že niekde čítal, že makrá sú zlé a napíše "anipattern". To môže pokojne byť aj nejaký pud sebazáchovy, a to je pochopiteľné, takže za to možno jednotlivec ani nemôže.
Ja viem, že máte v článku napísané, že makrá v C3 sú niečo iné ako makrá v C. Ale problém je, že je to napísané normálmym písmom a nie červeným a nie je to ani podtrhnuté trikrát.
Možno je problém aj to, že jeden jazyk použil označenie, ktoré už používal iný jazyk, ale poskytol inú, aj keď podobnú funkcionalitu.
To sa ale stáva, podobne nešťastného použitia už existujúceho pojmu sa dopustil istý nemenovaný pán, keď použil slovo objekt v označení techniky značne odlišnej od predchádzajúcich techník v starších jazykoch, ktoré tiež používali objekty a keďže to označenie bolo značne generické, ku ktorému by komunita dospela aj bez toho, tak vznikol trochu chaos v zmysle toho, že čo je to pravé a čo je už nepravé a z toho potom nekonečné debaty.
> Pretože inak sa môže stať, niečo na spôsob toho, čo tu niekto nedávno písal, že C++ nepodporuje rozhrania, pritom ich podporuje úplne od začiatku, iba sa tak nevolajú.
No ony ty rozhraní třeba v C# mají oproti C++ abstraktním třídám pár podstatných rozdílů. V C# dává smysl skládat děděním košatý strom dílčích rozhraní, kde se některé rozhraní můžou objevovat znova a znova. Některé standardní kontejnery jdou v tomhle hodně daleko. V C++ by to byl dost špatný nápad.
A teď rozhodněte. Je to stejná nebo jiná funkcionalita?
No ony ty rozhraní třeba v C# mají oproti C++ abstraktním třídám pár podstatných rozdílů.
Keď sú také podstatné a keď je ich iba pár, môžete ich vymenovať? Nech sa nebavíme v abstraktnej rovine.
V C# dává smysl skládat děděním košatý strom dílčích rozhraní, kde se některé rozhraní můžou objevovat znova a znova.
Naozaj to dáva zmysel?
V C++ by to byl dost špatný nápad.
Ktorý konkrétny dôvod tým myslíte?
Inak polymorfizmus, ako základná funkcionalita poskytovaná rozhraniami, realizovaný dynamickou väzbou v rámci dedičnosti, je v C++ dost špatný nápad
už dávno.
C++ umožňuje polymorfizmus riešiť aj inými spôsobmi.
> Keď sú také podstatné a keď je ich iba pár, môžete ich vymenovať?
No základní rozdíl je v tom, kde se nachází pointer na VFT. A od toho se odvíjí důsledky.
V C++ je rozhraní třída jako každá jiná. Ukazatel na VFT má v sobě a když dodám rozhraní, tak velikost třídy roste, protože musím přidávat ty pointery. Ale nemusím rozlišovat mezi pointerem na třídu a na rozhraní.
C# rozlišuje referenci na třídu a na rozhraní, protože pointer na VFT si v sobě táhne ta reference jako doplněk k referenci na třídu které se to rozhraní týká. Díky tomu není problém skládat mraky jednoduchých rozhraní dohromady.
V C++ jsem tenhle druh rozhraní potkal v podobě knihoven. Není to tak hezké na použití, ale zato se tam dají dělat věci co C# nedovolí i když tomu v principu nic nebrání.
No základní rozdíl je v tom, kde se nachází pointer na VFT. A od toho se odvíjí důsledky.
Rozhranie je prostriedok používaný pri realizácii jedného z druhov polymorfizmu. Jen to kontrakt, ktorý zaväzuje typ tvrdiaci, že rozhranie implementuje, poskytovať členov deklarovaných rozhraním. Nič viac, nič menej.
Ani špecifikácia rozhrania v C# neobsahuje nič o tom, že tabuľka funkcií je tam a tam, že prístup je optimalizovaný tak a tak, ani nič podobné. To je vecou špecifikácie CLR.
Pri takomto posudzovaní je nutné vedieť rozdeliť charakteristiky na hlavné a vedľajšie a to, čo uvádzate je implementačný detail, ktorý nesúvisí s tým, čo rozhranie je, ale s tým ako je realizované v rámci jedného konkrétneho behového prostredia.
V C++ to nikoho nezaujíma. To, že v C++ je rozhranie ako koncept implementované inak, nie je žiadny nedostatok, ktorý zabudol niekto vyriešiť, ale vedomé rozhodnutie, zabezpečujúce že prekladač má pod kontrolou úplne všetko a aj to, že volanie funkcie rozhrania bude rýchle, teda bez zbytočných skokov a vyhľadávaní.
Je to vedomá optimalizácia na rýchlosť, za cenu väčšinou zanedbateľného zvýšenia nárokov na pamäť. Pokiaľ má vo svete C++ s týmto niekto problém, má niekoľko ďalších spôsobov ako písať kód, s organizáciou dát vyhovujúcou modernému hardvéru a bez virtuálnych funkcií.
Podľa vašej logiky by rozhrania v iných jazykoch neboli rozhraniami, lebo sú implementované inak ako v C#.
Takhle obecný popis ale nestačí pro rozhodnutí, co s tím rozhraním má smysl dělat. Rozhraní v jiných jazycích mají diametrálně odlišné vlastnosti a podle toho se taky používají odlišnými způsoby. Rozhodně netvrdím, že jeden druh je správný nebo lepší.
Jestli by měla instance prázdného listu desítky nebo stovky bytů není zanedbatelný implementační detail. Vědomé rozhodnutí že volání funkcí bude rychlé a tak dál dělají implementátoři obou jazyků. Jen optimalizují v jiném kontextu.
V C++ se implementace odvíjí od toho, že rozhraní je třída jako každá jiná. A od toho, že můžu mít pointer na forward deklarovanou třídu, kdy ani nevím jestli má vůbec nejaké virtuální funkce.
V C# se zase implementace odvíjí třeba od toho, že pokud chci standardní kontejnery, tak musím optimalizovat pro mračna maličkých rozhraní. A že rozhraní není třída, takže se k nim není třeba chovat stejně.
Takhle obecný popis ale nestačí pro rozhodnutí, co s tím rozhraním má smysl dělat.
Nie som si istý, či s tým súhlasia autori špecifikácie C#, keď do nej tie implementačné detaily nedali. :-)
Rozhraní v jiných jazycích mají diametrálně odlišné vlastnosti a podle toho se taky používají odlišnými způsoby. Rozhodně netvrdím, že jeden druh je správný nebo lepší.
Ale stále sú to rozhrania v zmysle definície toho konceptu. Že sú v rámci jednotlivých jazykov a behových prostredí implementované inak je predsa samozrejmé. Musím trvať na tom, že je to implementačný detail. Závislý na prostredí.
Jestli by měla instance prázdného listu desítky nebo stovky bytů není zanedbatelný implementační detail.
V ktorom konkrétnom jazyku to tak je? Alebo naozaj chcete diskutovať o hypotetickom jazyku a ešte k tomu zle navrhnutom? Z akého dôvodu by tam vôbec mali byť desiatky alebo stovky bajtov? Márne hľadám nejaký racionálny dôvod.
že pokud chci standardní kontejnery, tak musím optimalizovat pro mračna maličkých rozhraní
Lenže to nie je vlastnosť jazyka, ale behového prostredia, ktoré bolo navrhnuté aj s cieľom vzájomne použiteľných binárnych komponentov písaných v rôznych jazykoch.
Inak teraz vôbec nechápem, čo ste chceli odpoveďou na môj pôvodný príspevok vlastne povedať? Že rozhrania pre jazyk, ktorý je schopný bežať na holom železe sú implementované inak ako rozhrania jazyku, ktorý beží na spravovanom virtuálnom stroji? No áno, ale je to nutné písať?
C++ je veľmi flexibilný jazyk a pokiaľ niekto chce, môže si implementovať rozhrania na ľubovoľný spôsob. Vlastné tabuľky ukazovateľov na virtuálne funkcie nie sú vôbec výnimočné. Videl som aj implementáciu traitov na spôsob Rustu. Navyše s nástupom statickej reflexie v C++26 sa možnosti nielen v tejto oblasti otvoria ešte viac.
Tak ma napadá, v čom je vlastne implementovaný CLR? Nie je náhodou to, čo umožňuje to, o čom píšete, napísané v C++? C++ bol primárnym jazykom pre COM, .Net je duchovným pokračovateľom technológií z rodiny COM a Don Box, meno známe minimálne od prelomu tisícročí, jedna z hlavných osobností COM aj .Net vraj povedal, že .Net je tým, čím si COM prial byť, keď vyrastie.
Nezdá sa vám byť pravdepodobné, že keď je v C++ možné napísať behové prostredie pre celú rodinu jazykov, vrátane rozhraní s vlastnosťami, ktoré spomínate, že je možné napísať rovnaké rozhrania aj pre samotné C++, keby o to náhodou niekto stál?
> Nie som si istý, či s tým súhlasia autori špecifikácie C#, keď do nej tie implementačné detaily nedali. :-)
Ony některé detaily vyplynou z toho, jak tu featuru sami používají. Proto jsem ukazoval ten košatý strom rozhraní u listu. Tohle použití vylučuje možnost, že by se ty rozhraní daly udělat pomocí abstraktních tříd jako v C++. To by ten list nabobtnal nejmíň na stovku bytů v samých pointerech na VFT.
Možná stojí za zmínku, že rozhraní jsou chápána jako pojmenování, a většina jejich smyslu se zužitkuje při compile-time (s výhradou nějaké té reflexe etc). V C++ je nějaká deklarativnost a hraní si na typy dost cizorodý element. Což ve výsledku může vést k nepochopení a zmatkům.
Opravdu mě zaskočilo, když mi jistý C++ programátor, který dokonce to C++ vyučuje s vážnou tváří tvrdil, že dědičnost slouží k reusable kódu. Ne, že "se používá", nebo "dá se zneužít", nebo "dobře poslouží".
> že dědičnost slouží k reusable kódu
Neverte mu, tvrdi to zle.
V modernom chapani OOP sa uprednostnuje kompozicia na znovupouzitie kodu, dedicnost sa ma pouzit na polymorfizmus.
PS: Rozhrania v C# vykonu nevadia, lebo JIT ma schopnosti devirtualizacie. Dokonca aj pri nativnej kompilacii. A v pripade, ked si to chce programator vynutit, tak proste pouzije generika
void Method1<T>(T strategy) where T : IStrategy { }
> Neverte mu, tvrdi to zle.
Není co věřit.
Na druhou stranu s C++ je potíž, že on těch nástrojů moc nemá. Když nemá ani rozhraní, tak těžko po něm chtít něco takového jako jsou třeba traity. Takže já samozřejmě chápu, že se ta dědičnost na reusable kódu používá (samozřejmě v situaci, kdy kompozice není vhodná), protože nic jiného prostě není. A ti programátoři jsou pak kolikrát mentální obětí svého jazyka, podobně jako se to stává u Javařů a kdekoliv jinde.
> PS: Rozhrania v C#
C# je bazmek :-)
Na druhou stranu s C++ je potíž, že on těch nástrojů moc nemá. Když nemá ani rozhraní, ...
Už minule ste dostali celkom obsiahlu odpoveď, že C++ rozhrania samozrejme má. Podobná informácia je v komentároch k tomuto článku.
Nechcete konečne vysveliť prečo si myslíte to, čo si myslíte?
... tak těžko po něm chtít něco takového jako jsou třeba traity.
C++ má niečo lepšie, mechanizmus, ktorým sa tie traity dajú implementovať. Alebo ľubovoľný iný mechanizmus volania funkcií. Aj to je mimochodom uvedené v komentároch tohto článku. Iba naozaj veľmi ťažko si viem predstaviť, že ste to nečítali.
Možná stojí za zmínku, že rozhraní jsou chápána jako pojmenování, a většina jejich smyslu se zužitkuje při compile-time (s výhradou nějaké té reflexe etc).
Možno stojí za zmienku aj to, že môže dôjsť k rozdielu v tom ako niečo chápe jedna konkrétna osoba a ako je to definované v rámci počítačových vied.
V C++ je nějaká deklarativnost a hraní si na typy dost cizorodý element.
Pokiaľ myslíte závislé typy, tak o tom sme sa už predsa kedysi bavili. V zmysle, že chápem želanie mať všetky možné kontroly v rámci jedného jazyka a počas jedného kroku zostavenia, ale často sa ten jazyk potom stáva pre normálnych programátorov ťažko pochopiteľný a vo finále jeho náročnosť presahuje náročnosť použitia viacerých overovacích a formalizačných technológií.
C++ má jednoducho takú podporu typov, na akej sa komunita zastúpená pracovnou skupinou WG21 pri ISO dohodla. Ak sa ukáže, že by bolo prínosné rozšírenie typového systému, dá sa predpokladať, že sa do C++ dostane.
Opravdu mě zaskočilo, když mi jistý C++ programátor, který dokonce to C++ vyučuje s vážnou tváří tvrdil, že dědičnost slouží k reusable kódu. Ne, že "se používá", nebo "dá se zneužít", nebo "dobře poslouží".
Programovací jazyk predsa nie je zodpovedný za to, že má niekto nedostatočné znalosti, prípadne, že, ako v tomto prípade, má neodostatočné znalosti v programovaní ako takom.
> Naozaj to dáva zmysel?
No v kontextu toho jazyka bych řekl, že jo. Vemte si, jak vypadají třeba standardní kontejnery :
public class List<T> : System.Collections.Generic.ICollection<T>, System.Collections.Generic.IEnumerable<T>, System.Collections.Generic.IList<T>, System.Collections.Generic.IReadOnlyCollection<T>, System.Collections.Generic.IReadOnlyList<T>, System.Collections.IList
Samozřejmě že něco, co se v C# dělá rozhraníma, by se dalo výrazně líp udělat pomocí featur z jiných jazyků. Cynicky bych řekl, že půlka GoF patternů jsou komplikované OOP implementace principielně jednoduchých featur z jinýh jazyků. :)
Že GoF tak trochu smrdí jsem dospěl taky. Ale beru to tak, že náš obor je mladý, a prostě někdo musel vyfukovat tabákový kouŕ do umyvadla s vodou.
C++ je stará záležitost. Nepřekvapí, že prostě některé věci nemá, nebo neumí. A vzhledem k filozofii jazyka a očekávání uživatelů některé věci nikdy umět nebude.
Java detto.
C# je bazmek.
C3 nevypadá, že by někdy byl můj oblíbenec, ale je super to pozorovat. Držím mu palce.
Že GoF tak trochu smrdí jsem dospěl taky. Ale beru to tak, že náš obor je mladý, a prostě někdo musel vyfukovat tabákový kouŕ do umyvadla s vodou.
Celkom by ma zaujímalo čím ste k rozvoju IT prispeli vy, keď autori knihy, ktorí v rámci nejakého historického kontextu, v ktorom veľa vecí, ktoré používame teraz, neexistovalo alebo neboli široko známe, urobili to, čo bolo možné, podľa vás iba vyfukovali tabakový dym do umývadla s vodou.
Uvedomujete si vôbec, že tá kniha bola písaná vtedy, keď ešte vlastne neexistoval ani internet??
C++ je stará záležitost. Nepřekvapí, že prostě některé věci nemá, nebo neumí. A vzhledem k filozofii jazyka a očekávání uživatelů některé věci nikdy umět nebude.
C++ bude vedieť veci, na ktorých sa komunita zhodne, že ich vedieť má.
Mimochodom, neboli ste to náhodou vy kto tvrdil, že C++ nemá rozhrania?
No v kontextu toho jazyka bych řekl, že jo. Vemte si, jak vypadají třeba standardní kontejnery
To, že niečo používa štandardná knižnica jedného jazyka neznamená, že niečo dáva zmysel všeobecne alebo že je to univerzálne riešenie, ktoré by mali podporovať všetky jazyky.
Dôležitý je naozaj kontext. Tým je v tomto zmysle podstata polymorfizmu v C# alebo možno v celom .Net, ktorý je dynamický. V tom kontexte to asi zmysel dáva.
C++ dynamický polymorfizmus podporuje, ale má aj výkonnejšie formy polymorfizmu. Keby ale niekto chcel písať niečo také v C++, tak môže, ale malo by to zmysel iba keby chcel vytvárať niečo na spôsob dynamického behového prostredia alebo dynamicky zavádzaných binárnych komponentov. Ale to sme už predsa dávno mali. Na platformách Microsoftu sa to volalo COM a opustili sme to niekedy okolo roku 2000. Na základe skúseností s tým vznikol .Net.
Poznámku o GoF nemá zmysel komentovať, určite chápete, že aj v tom prípade je dôležitý kontext. Historický.