lot of progress in language design - tagged unions and interfaces starts to look well, as well as error handling and many minor things too

This commit is contained in:
Richard Thier 2024-06-09 20:26:18 +02:00
parent 7b1e0e39b4
commit 1ce293a4b1

508
BASED.md
View File

@ -2,6 +2,10 @@
Ha megcsinálom a "strukturált formában tárolt programszöveges" ötletem, akkor egy saját nyelvet is implementálnák vele, ami "nem memory safe, hanem nehezebbé teszi a memória hibákat" - tehát nem lenne bonyolult borrow checker hozzá.
Megj.: Május elsején kéne az első igazi változatát megcsinálni? Ugyanis a BASIC akkor futtatott először kódot:
https://prog.hu/hirek/6677/basic-60-eves-programozasi-nyelv-szuletesnap-evfordulo-darthmouth-kemeny
# Elnevezések
- *.basd
@ -10,12 +14,45 @@ Ha megcsinálom a "strukturált formában tárolt programszöveges" ötletem, ak
- Based Advanced Simple Effective Devlang
- - - - -
Based - szleng: olyan önálló vélemény vagy tett, ami ki mer állni magáért és nem feltétlenül a trendeket követi, és ezt nem fél kinyilatkozni. Azaz van egy alapja/bázisa (base) annak amit vallasz, ettől leszel bázisolt (based). Nem feltétlenül elfogult vélemény, de mivel a tömeggel nem mindig ért egyet, ezért azt sokan vitathatják.
Magyarul: "Megalapozott" vagy esetleg "Alapos" / "Alapozott" - Magyar szlengben (másra) még használt: "Alap" (alap, hogy ismerem!)
Based - Slang: true to ones self or secure in ones beliefs regardless of what others think, especially when those beliefs fall outside of the mainstream.
For ex.:
Her presentation was so based—did you see the teacher's expression? Priceless!
# Operátor precedencia
https://www.youtube.com/watch?v=fIPO4G42wYE
^^Ez baromi hasznos és szerintem nem csak rekurzív leszállásnál lehet szerintem ez!
https://www.youtube.com/watch?v=AikEFhCaZwo
1. Zárójel, adattag-hozzáférés, függvényhívás, konstruktor, alaptípus-konverzió, postfix ++ és --, typeof, sizeof
2. Pozitív és negatív operátorok, logikai és bináris tagadás, prefix ++ és --, $ a típusra és kifejezésre
3. Szorzás, maradékos és maradék nélküli osztás, modulus
4. Összeadás, kivonás
5. Bit-eltoló operátorok
6. Kisebb, nagyobb (vagy egyenlő), array(ptr, n), asif, is (típus equals check generic-re), islike(???)
7. Egyenlő és nem egyenlő operátorok
8. Logikai/Bináris ÉS
9. Logikai/Bináris XOR
10. Logikai/Bináris VAGY
11. Feltételes ÉS
12. Feltételes VAGY
13. Feltételes operátor (?:)
14. Értékadás
Ez is esetleg király lehet (Shunting Yard Algorithm):
https://www.youtube.com/watch?v=unh6aK8WMwM
Meg ez is (basic block-os módszer):
https://dengking.github.io/compiler-principle/Optimization/Basic-block/
# OOP helyett: handler
handler Vector<T> {
@ -25,6 +62,7 @@ Ha megcsinálom a "strukturált formában tárolt programszöveges" ötletem, ak
^^Ezek "kezelik" a memóriákat és resource-okat RAII-módon, konstruktorral és destruktorral.
Szerintem a struct-hoz képest is jobb szó.
Lesz továbbá tagged union is - lásd lejjebb - és interfészek (ami tagged union-á fordul le, hacsak ki nem optimizálja a fordító).
# Memória és resource kezelés
@ -32,7 +70,7 @@ A lényeg ez (kb. "kötelező" RAII + extra szabályok dangling ref / pointer el
- referenciákat lehet paraméterként megadni elvárt típusnak ÉS visszatérési értékként is metódusoknál
- De referenciát eltárolni struct mezőben nem lehet - sima változóban félreteheted magadnak a stack-en ha nem akarod mindig kiírni az expression-t.
- Pointerekre meg van egy ownership modell, hogy a class-hoz / struct-hoz tartozik egy "resources" vagy "pointers" blokk... Valszeg csak külön syntax van erre, nem akarok ilyen hosszú szavakat kiírva látni, mert öli a produktivitást, hanem mint a C++ban pl van az inicializálós rész a konstruktornál, úgy a class-nál valami hasonló kis syntax erre...
- Pointerekre meg van egy ownership modell, hogy a handle-khez (class/ struct megfelelője) tartozik egy "resources" vagy "pointers" blokk... Valszeg csak külön syntax van erre, nem akarok ilyen hosszú szavakat kiírva látni, mert öli a produktivitást, hanem mint a C++ban pl van az inicializálós rész a konstruktornál, úgy a class-nál valami hasonló kis syntax erre...
- Ugye pointereket vissza tudnak adni és el tudnak fogadni paraméterként @resource-nak és @release-nek jelölt függvények (mint pl. a malloc meg a free).
- Az ilyen (így jelölt) függvényeket, csak konstruktorból és destruktorból tudod meghívni!!!
- De persze a konstruktor beállíthat a pointerednek valami értéket - nem kell malloc-ból inicializálja meg! és ugye nem kell free-szerű dolgokat hívnia rá...
@ -59,6 +97,10 @@ A @unsafe-t lehet hogy class/struct pointer blokkjára is engedhetek rátenni, a
Mondanom se kell: GC nyilván nincs így!
Ez meg szerintem fasság - nem jó érvek:
https://dept-info.labri.fr/~strandh/Teaching/MTP/Common/Strandh-Tutorial/need-for-garbage-collection.html
## Custom allokátorok / arénák
A go-hoz újabban ajánlott "arénás" modell-t is lehet hogy valahogy alkalmazhatnánk - tehát hogy azzal oldjuk meg a custom allokátor kérdést... De szerintem egyszerűen úgy kell kialakítani a standard libet / kódokat, hogy template paraméter szerűen átvegye milyen @resource és @release függvényeket használ - tehát nem konkrétan malloc-ot, meg free-t mondjuk hanem csak azoknak megfelelő deklarációs függvényeket... Ezt C-ben is szoktam csinálni (pl. a kismap-ban van ilyen), de ugye ott függvény pointerekkel, amiket vagy eltárolok (de akkor runtime költsége van), vagy ha a perf fontos, akkor pl. a kismap esetén azt szoktam, hogy ezeket nem csak konstruálásnál, hanem minden hívásnál át kell adni ami használja - azért mert látom, hogy a compiler be tudja inline-olni, ha egy static inline force-inline stb. függvényt adok át neki function pointernek... De ez ugye "trükközgetés" feleslegesen és jó lenne, ha nyelvi elemként tudnánk ezt. Esetleg érdemes a hagyományos generik / template témától ezt különszedni? Nem biztos, lehet hogy azzal kezelendő. Cpp esetén template-ekkel szoktam ezt csinálni...
@ -69,7 +111,26 @@ Megj.: A "generational arenas" esetleg legyen támogatott? Ez egy olyan dolog, h
# Párhuzamosság / Multithreading
Jó lenne TALÁN, pár experimental dolog:
Az "async"-ot szerintem felejtsük el... Én undorítónak tartom - a corutin támogatás is jobb annál...
Esetleg lehetne egy direkt párhuzamosságra csinált "al-programnyelv" / DSL?
Mint ez:
https://github.com/HigherOrderCO/Bend
+ Egy rust-os példa "könnyen elrontható aszinkronitásra:
https://youtu.be/NaytZOiX3fs?t=780
(Van egy nem-async függvény, ami hibázhat (errors as values) és result object-et ad, de tudod, hogy nem hibázik...
... ekkor ha async-á írod át mi lesz, ha előtte '_'-nek adtad értékül a result-ját? Mostantól a future-t fogod...
... és a compiler nem szól, hogy elfelejtetted az await-et! Szóval kb. elcsesz neked mindent!!! Ilyesmi is baj...
... nem csak az "elméleti" biztonság fontos egy nyelvben, hanem a pszichológiai is!!!)
Esetleg a go-ból átemelni dolgokat? Meg Java JCIP-ből? - De ez lehet talán library szinten is / makró szinten is...
De ha ezen nem is, akkor is jó lehetne TALÁN, pár experimental dolog:
- Azonos logikai (és fizikai) CPU-ra confine-olt thread-ek, amiknél így nem kell lock-olás (greenthread szerű, esetleg a go arénákhoz hasonlóan scope-olt allokáció hogy hova spawnoljanak).
- A go féle dolgok jók - kérdés, hogy java-szerűen ezek lib elemek legyenek, vagy mint a go-nál inkább nyelvi elemek.
@ -112,20 +173,74 @@ Legyen a kismaphoz hasonló - tehát (template) paraméterként kapnak a handle-
## Jobb pointerek
Valahogy jó lenne elérni, hogy a default a "restrict" kulcsszóhoz legyen közelebb.
Valahogy jó lenne elérni, hogy a default a "restrict" kulcsszóhoz legyen közelebb. Szerintem tömbre a default a restrict, de
a pointerre ki kell írni?
* Talán a tömbök külön típussá emelése ebben már eleve segít
* Mivel az ownerek kezelnek csak tömböket, ők adhatnk ki referenciákat és range-eket, de azok ugye nem alias-olnak be könnyen
* Talán a tömbök külön típussá emelése ebben már eleve segít - igen... legyenek továbbá mérettel is ellátva stb. lásd ott
* Mivel az ownerek kezelnek csak ptr-eket, ők adhatnk ki referenciákat és array-eket, de azok ugye nem alias-olnak be könnyen
* A restrict kulcsszó támogatása a minimum - de talán kéne gondolkozni mit lehetne még kihozni, hogy jobb legyen!
* Ugye mivel van külön "tömb" típusunk, ezért arra is lehetővé kéne tenni a restrict-et pl. paramétereknél, különben bakis lesz.
A tömböknél a restrict lehet ESETLEG a default - de ennek fényében kellene unrestrict kulcsszó rájuk!
### NonOwningPointer - smartpointer
Ezt szerintem érdemes C++ template-el előre megcsinálni! A lényeg az, hogy kreálni lehet a dolgot - tehát van egy factory-szerű,
class Owner {
private:
std::vector<int> vec = {1, 2, 3};
// Has a vector of pointers to children
// So when this go out of scope we can set child pointers backpointers to nulls (indicating non-living)
OwnPtrFactory<int> fac;
public:
// or access(..) etc.
// NownPtr has a pointer to the factory's own elem and on its move assign / move constructor calls updates facs child vec's
// pointer. Caller can ask the pointer if owner is already deleted or not. Maybe should be threadsafe?
NownPtr<int> getMid() {
return fac.create(&vec[1]);
}
};
Ezt követően a használati ponton olyasmi történik, hogy:
class User {
void f(..) {
...
// Safe használat
NownPtr mid = owner.getMid();
mid.if_exists([](int &i) {
... i is accessible ...
});
// Unsafe használat (esetleg nálam @unsafe és konstruktor/destruktor esetben?)
int j = *mid + 40;
// Unsage-nél is lehessen kérdezni
if(mid) {
int k = *mid;
}
...
}
}
Ez tehát úgy látom C++ nyelven is lehetséges, egyedül mozgatáskor tűnik kicsit is "lassabbnak" meg ugye ez egy "fat" pointer, ami
2x pointernyi méretet használ fel... az egyik maga a mutatott terület címe, a másik a "factory"-ban lévő vektor megfelelő elemére
mutat rá - itt vigyázni kell: a vektor resize esetén invalidálódik ez! Kéne valami "stabil-vektor" ami blokkos és a resize-nál NEM
invalidálódik, de nem is egyesével, hanem blokkosával láncolt lista!
Ezt esetleg nem is nyelvi elemként tennénk be, hanem library-ként a BASED-be is, az talán jobb?
## Jobb tömbök
Néhány dologra van szerintem szükségünk:
* (Pointer + méret / range) jellegű tömb történet, ahol a méret / range lekérhető a típusból ADA-szerűen
* Range subtype (ada szerűen) külön a típusrendszerben - hajlok arra, hogy 0..méret lehessen csak range viszont (praktikus: dyn)
* Dinamikus esetben, hívhasd az ilyen függvény paramétert sort(range(array, 42)) történettel (ahol array egy pointer, 42 méret).
* (Pointer + méret / range) jellegű tömb történet, ahol a méret / range lekérhető a típusból ADA-szerűen (.len) és az elemtípus is!
* Range subtype (ada szerűen) NEM kell külön a típusrendszerben, mert hajlok arra, hogy 0..méret lehessen csak (praktikus: dyn)
* Dinamikus esetben, hívhasd az ilyen függvény paramétert sort(array(ptr, 42)) történettel (ahol ptr egy pointer, 42 méret).
A fordító két függvényt is generál ilyen függvényekből. A típusrendszer szerint azt a függvényt hívjuk meg, amelyik gyorsabb!
Igazából ha fordítási időben ismert méretű tömbbel hívjuk meg, akkor a "sima" függvény hívódik, kivéve, ha ez valami linkelt lib,
@ -135,12 +250,14 @@ meg kellene oldani...
A lényeg, hogy a "tömb" típus az pointer + hossz minden esetben, de ahol tudja, a fordító kioptimalizálja a történetet!
Ha valami mást akar az ember, például performancia okokból, akkor simán adjon át egy pointert (és mondjuk null termináns).
Ha a "range" második paramétere nem fordítási idejű konstans szám, akkor ott is a dinamikus függvényt hívjuk majd meg.
Ha az "array" második paramétere nem fordítási idejű konstans szám, akkor ott is a dinamikus függvényt hívjuk majd meg.
A range, mint altípus megadás lehetővé teszi, hogy dolgok "osztozzanak" a méreten - pl. ECS egy játékmotorban, stb.
Emellett természetesen a standard library-ban kell "vector" típus és az tudjon range-t is adni neked.
A range, mint altípus megadás lehetővé tenné, hogy dolgok "osztozzanak" a méreten - pl. ECS egy játékmotorban, stb.
De mivel nem tól-ig, vagyis ADA-szerű range-jeink vannak, simán csak méret, ezért ha ilyet akarsz konstans számként tárolod.
Szerintem az így kapott kód "már kellően biztonságos" általában.
Emellett természetesen a standard library-ban kell "vector" típus és az tudjon array-t is adni neked.
Szerintem az így kapott kód "már kellően biztonságos" általában. A vektorra debug build esetén legyen range check kötelezően!
Lásd még:
@ -153,7 +270,7 @@ ui.: Ha nem lesz operátor overload, akkor szerintem legyen több dimenziós tö
Erről még nem vagyok meggyőzve, de néha hasznosnak tűnnek:
* begin és end klózok, melyek több switch-et körbefognak és mindegyikük előtt és után végrehajtódnak
* unchecked switch - ahol sose generálódik "mi történik, ha nincs olyan case" code path / jmp. Erre viszont kéne compile check is!
* checked switch - ahol sose generálódik "mi történik, ha nincs olyan case" code path / jmp, de compile time check: mindent kezelsz?
Az előbbi egy kis usability feature és ugye a jump táblás megoldáson annyit változtat, hogy inline-ol oda közös részeket, mintha
azok egy függvénybe forceinline lennének kitéve: tehát igazából C kódot tudunk generálni, ami ezt tudja.
@ -163,10 +280,76 @@ match statement-jéhez erre, hogy ne hagyjál ki valami esetet, akkor az pont a
C kódot is a fordítónkkal, de akkor nem lesz "gyorsabb" a generált kód - saját fordítóval elvileg lehet gyorsabb is majd, mert
ugyebár így tényleg teljesen jump tábla fog keletkezni.
Több-dimenziós switch:
// checked meand all cases must be handled, unchecked means not all!
unchecked switch(shiftState, button) {
case (true, 'a'):
run_left(); break;
case (true, 'w'):
run_up(); break;
case (true, 's'):
run_down(); break;
case (true, 'd'):
run_right(); break;
case (false, 'a'):
walk_left(); break;
case (false, 'w'):
walk_up(); break;
case (false, 's'):
walk_down(); break;
case (false, 'd'):
walk_right(); break;
}
^^Viszont szerintem ezt talán feláldozhatjuk a "nem kell break-t kiírni, de lehet a case mögött több esetet sorolni".
NEM! Ne áldozzuk fel, mert akár lehet úgy is, hogy a ':' helyett más jelezze az összevont eseteket:
case 'A'>
case 'B':
...
case 'C':
++c;
fallthrough;
case 'D':
...
TL;DR: nem lesz break; statement, de lessz fallthrough; statment!
Megj.: Ha nem lesz break; - akkor viszont kell szerintem "nop;" is - tehát kell parancs a semmittevésre, hogy üres case lehessen!
## Ciklus break és continue helyett + goto kérdéskör
Legyenek ezek: enditer; és nextiter;
Esetleg: Simán válasszuk azt, hogy csak goto lesz és csőváz?
Mindenesetre a GOTO-ból legyen számított goto is (ez gcc / clang extension).
Miért?
* Hát a continue-t sose volt könnnyű érteni (a szó alapján)
* A break;-t nem célszerű használni, mert a switch-ből kikerül és így aki ott "reflexből" beírná, az break-elne a kinti ciklusából!
## Visszatérési értékek, több visszatérési érték
Legyen-e ilyen még a sima errors as values mellett? Mindenképp mellett, mert az error kezelés kikényszerítő plusz hatású, de
amúgy más tekintetben simán ilyenre fordulhatna a compileren belül, ha ez IS van!
(int, int) swap(int a, int b) {
return b, a;
}
Szerintem hasznos lehet mondjuk állapotgépeknél, a tagged enum-okkal együtt, ahol az állapottól függően a tagged enum a következő
állapotot ÉS mondjuk lexer esetén a nemterminális token-t is visszaadhatjuk (ha lett). Ezzel megspórolható egy out param!
Ha lesz korutin, ott a "hagyományos" értelemben kéne a yield;-et használni szerintem.
## Pattern matching
Ez jó kérdés, hogy legyen-e. A rust-os match statement-nek azért vannak előnyei. Megfontolandó.
^^Szerintem a "checked switch" jellegű dolgok első körben elegek lesznek? Sőt a checked legyen szerintem a default!
## Öröklődés, polimorfizmus
- Nem lesz altípusos polimorfizmus.
@ -189,8 +372,28 @@ Például:
};
// Megj.: Elfogad A a[42]; jellegű tömböt!
void sort(int-like elements[]) {
...
void bubblesort(int-like arr[]) {
int n = arr.len;
int i, j;
bool swapped;
for (i = 0; i < n - 1; i++) {
swapped = false;
for (j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
// Swaps full handles (or when simple ints, just ints)
$arr.etyp tmp = $arr[j]; // Can access 'real' type
$arr[j] = $arr[j + 1]; // can operate on 'real' type and this works also for
$arr[j + 1] = tmp; // move, copy, functions (ad-hoc polymorphism), etc.
swapped = true;
}
}
// If no two elements were swapped by inner loop,
// then break
if (swapped == false)
break;
}
}
* Vegyük észre, hogy ez csakis fordítási idejű polimorfizmus ebben a formában! Ki kell tudnom számítani az offsetet!
@ -206,6 +409,187 @@ az egész handler természetesen azon a ponton!
Megj.: Több ilyenje is lehet egy handle-nek! Nyugodtan lehet `like string name;` is itt!
Megj.: Szerintem beleférhet még: `like readable int get() {..}` és/vagy esetleg `*like int get() {.. return pointer ..} ?
** FONTOS **
Még fontos azt is látni, hogy ilyen "int-like" vagy "MyHandleType-like" típusok csak deklarációkban lehetnek, a sima like kulcsszó
pedig csak definícióban / implementációnál! Tehát NEM lehet csinálni int-like arr[]; tömböt - csak mondjuk A arr[]; tömböt, de ez
utóbbit viszont át lehet adni paraméterként a sort(..) függvénynek így!
## Tagged union
tagged union AB {
int szam:
void triple() { this->szam *= 3; }
int integral() { return this->szam; }
void add(int other) {...}
float lebego:
void triple() { this->lebego *= 3; }
void add(float other) {...}
double legego2:
void triple() { this->lebego2 *= 3; } // kotelezo azonos szignaturaval ismetelni, amire nincs default!
string nevptr:
void triple() { this->nevptr = this->nevptr + this->nevptr + this->nevptr; }
default:
// Statics only work as defaults
static niniple(AB &a) { a.triple(); a.triple(); }
// Can add default implementation switch-cased with either checked or unchecked switch-es
int integral() {
//
unchecked switch(this->tag) {
case lebego:
...
case lebego2:
...
}
}
}
AB ab;
ab.szam = 42;
assert(ab.tag is int);
ab.nevptr = "kula";
assert(ab.tag is string);
Másik lehetőség (esetleg a kettő párhuzamosan is lehet?):
tagged union DynamicNumber {
int i;
float f;
string to_string(string s) {
switch(this->tag) {
case int:
...
case float:
...
}
}
static DynamicNumber from(string s) {
// ...
}
}
Talán jobb volna, ha csak ez a "második" forma létezne? Lásd még tagged enum esete?
Harmadik - legjobb? - lehetőség (ha tagged union-ban vagyunk, nem kell a switch-et kiírni):
tagged union DynamicNumber {
int i;
float f;
string to_string(string s) {
int:
...
float:
...
}
void add(DynamicNumber &other) {
int:
...
float:
...
}
static DynamicNumber from(string s) {
// ...
}
}
Negyedik lehetőség - második + interfészes ötlet, ami talán még jobb:
interface IGameObject {
spawn(float x, float y);
update(float gametime);
}
handle Player implements IGameObject {
spawn(float x, float y) { ... }
update(float gametime) { ... }
}
// ... Zombie...Boss...
// The "implements" clause is optional here
tagged union GameObject implements IGameObject{
public:
// All of them need to implement the interface and calls get delegated to the proper one!
Player p;
Zombie z;
Boss b;
// One can add methods as-is
string to_string(string s) {
switch(this->tag) {
case Player:
return "A jatekos";
default:
return "Egy ellenseg";
}
}
protected:
static void testAll() {
...
}
}
^^Ez a harmadik változat abban is jó, hogy könnyebben játszik össze a public-private kulcsszókkal is!
^^Mellesleg így kikerülhető, hogy default-olhassák (mert itt szerintem nem szabadna hagyni).
Ahogy látható, ez a sima "union" kulcsszó egy "változata", amikor is a méret az a méretek maximuma + tagméret.
A tag alapján lehet szépen switch-elni rajta - de a dolog támogat egyfajta "polimorfizmust" is: Lehetnek az adott mezők után így
kettőspontos módszerrel (és beljebb identálni illik ilyenkor) az ahhoz köthető függvények - de ez nem kötelező!
Ilyenkor egy "triple()"-t hívni rá, az helyben "legenerálja" a switch-case-t és a megfelelő hívódik meg!
FONTOS: Talán érdemes volna kötelezővé tenni, hogy minden "esetnek" legyen meg minden implementációja, vagy hogy kettőspont nélkül
leírt függvény esetén neked kelljen magadnak switch-case-elned a történetet (és az fusson le). Ezzel egy adott függvénynek vagy
minden esetre kell legyen definíciója, vagy az únió eseteitől függetlenül álljon ott - de runtime error-t nem szeretnék!
## Interfészek
Zsolt vetette fel, hogy neki azért öröm töltené el a szívét, ha lehetne egy "kicsit OO style" azért annyiban, hogy egy tömbben tud
keverni különböző típusú dolgokat, amiknek közös az interfésze és kapni ilyeneket függvény paraméterként...
Ez valamilyen szinten jogos elvárás és az únió részben tudja is ezt, mert csinálhat az ember egy tagged union-t amiben ő maga
sorolja fel így a típusait amik a tömbben lesznek és a fun() függvényeikben simán this->fun() átírányít / delegál akkor.
No de! Ennél tudunk jobbat is! Mert ez nem kezeli azt az esetet, ha egy library-t akarok írni és ezt forrással együtt kiadni!
Ugyanis olyankor a lib írójaként én megszabom mely dolgokat veheti fel a tagged union - tehát előre kéne tudnom a use case-eket!
Ez nincs így! Ellenben a fordítót kihasználhatjuk arra, hogy generálja ezeket a tagged union-okat le, az alapján, hogy kik
implementálnak egy interfészt!
Első változat:
interface IStringifyable {
string stringify();
}
handle A implements IStringifyable {
...
}
handle A implements IStringifyable {
...
}
// Ez ilyenkor egy tömb, aminek az elemei igazából tagged union elemek,
// a build-ben meglévő összes azt implementáló dolog alapján...
void process(IStringifyable arr[]) {
...
}
Második változat:
Lásd tagged union-oknál a "negyedik változat". Szerintem ez a legszebb!!! A tagged union tud implementálni interfészt és így
sokkal egzaktabban történik ugyanez "háttérben titkos tagged union generálás nélkül" - na így lett letisztult!
+ ha nem tömbről van szó, akkor a hívási ponton az egy lightweight generic támogatás / ad-hoc polimorfizmus is (kell-e vajon?)
Az interfészekre lehet többszörös öröklődés (egymás közt is, meg itt is).
FONTOS: A shared object-ek között NEM ajánlódnak ki ilyen jellegű típusok! Csak struct-ok!
## Láthatóság
* public
@ -266,6 +650,10 @@ annak azt mondom: az örökléshez meg nem kell semmi ilyesmi, szóval eddig pon
- FONTOS: Legyen már lehetőség függvényt adni template paramnak (int-et is, meg típust is, de függvényt is please).
- Az if constexpr (...) { .. } az tetszett a modern C++ból!
Érdekes írás:
https://willcrichton.net/notes/the-coming-age-of-the-polyglot-programmer/
## Strukturált formában tárolt programszöveg (tm) fordítás
Az első pontot nehezebb érteni, ha nem ismerjük a kontextust: Van ez az ötletem, ami forth-szerű, de faszerkezetben strukturált "compiler", ahol a forth szavaknak lehetnek "gyerekei" és ilyen blokkjai. A (...) blokk is csak egy block, mint a [..], vagy akár a {...}. Ugyanúgy forth szavakat definiálunk és meg tudjuk mondani, hogy a compiler az adott szóhoz érve mit csináljon. Tehát konkrétan scriptelni tudjuk a fordítót. Most nem mennék bele ebbe, de pár kiegészítéssel (papíron ez részletesen is megvan) kb. az összes "értelmes" nyelvet lehet így "parzolni". A fordításban csak egymásba pipe-olva állítjuk elő a következő és következő változatokat több pass-ban - és az utolsó pass ugye mondjuk fizikai fájlba ír, vagy interpreter esetén mondjuk végrehajt. Ezzel debuggolni is tök könnyű a "compiler/interpreter"-t vagy nézni hol mit optimalizál.
@ -274,12 +662,80 @@ Viszont ha ezt megcsinálom, akkor ezt az itt leírt nyelvet, ezzel implementál
Megj.: A "néhány kiegészítés / részlet" azok olyasmik mint hogy nyilvántartjuk az indentálást (mind a hibajelzések, mind az ilyen python-szerű fos nyelvek miatt), kicsit trükközni kell a <..> jellegű dolgoknál (pl. generics, template), vagy épp hogy a "fordító scriptelése" azt is jelenti, hogy tudunk előre-olvasni vagy akár kézzel karakterenként olvasni és úgy mozogni az "input szöveg nyelén" ahogy Csörnyei mondaná Magyarosan a "handle"-t... Tehát ezzel tényleg nagyon testre szabható mi történik. Alapból stack-ed is van, mint a forth-nak, meg mellé vannak a fordító adatszerkezetei is (bár esetleg azokat csak akkor éred el, ha speciális jellegű szót definiálsz).
## Tagged enum / smartenum
Ez hasonlít a taged union-hoz, de a union esetén a típus határozza meg a TAG-et.
Ebben az esetben a típus mindig azonos és valamelyik integer alaptípus (pl. int).
Példa:
tagged enum(int) FileError {
NOT_FOUND = 0:
const char *msg() { return "File not found!"; }
PERMISSION_ERROR = 1:
const char *msg() { return "Not suitable permissions!"; }
}
Másik lehetséges alak:
tagged enum(int) FileError {
NOT_FOUND = 0;
PERMISSION_ERROR = 1;
const char *msg() {
switch(this->tag) {
case NOT_FOUND:
return "Not suitable permissions!";
case PERMISSION_ERROR:
return "File not found!";
}
}
}
Lényegében a java smartenum-jához hasonló dolog, vagy ahhoz a smartenumhoz amit én csináltam az impexlib C++ kódbázisban!
Megj.: Itt is talán a második formát kéne csak engdni? Lásd "tagged union"-nál lévő két alak közül a második?
Megj.: Esetleg megfontolni miként segíti ez az állapotgépek használatát / parzer építést? Esetleg a 2D switch-el?
## Exception és hibakezelés
- Exception-ök nincsenek
- Go és rust-hoz hasonló hibakezelés az szerintem jó ötlet, de egy-az-egyben egyik se tetszik ebből a kettőből. Jó ha van valami syntax sugar és az is, ha kötelezővé tudod tenni az error-t is visszaadó függvény hívásánál a hibakezelést, vagy tovább propagálást.
- Stack unwinding, meg ilyesmi sincs így: egyszerűen syntax sugar van, hogy fordításkor kikényszerítheted a hiba lekezelését ha valaki hív téged. Az egyetlen kérdés, hogy mi történik a tagged union-os esetben ha polimorfizmus van és más-más esetben lehet, vagy nem lehet hiba... hát a válasz az, hogy a függvény deklarációban benne kell legyen a hiba szóval a hiba maga itt nem polimorf (vagyis csak ugyan ennyire, de nem lehet olyan, hogy valamely tagged union variáns olyan hibát dob és úgy, ahogy az nincs interfészben jelölve).
A legjobb egy Zig-szerű megoldás lenne - jelenleg ezt mondanám a választásomnak:
// TODO: Just example, not real code
tagged enum(int) FileError {
NOT_FOUND = 0:
const char *msg() { return "File not found!"; }
PERMISSION_ERROR = 1:
const char *msg() { return "Not suitable permissions!"; }
}
FileHandle open_file(const char *path) onfail FileError {
...
fail FileError.NOT_FOUND; // error handlers can also fail again: both their own errors or "fail err;" to re-fail.
...
}
void testcode() {
// onfail clause is mandatory if function declaration has an onfail - unlike with exceptions
// Its "errors as values" philosophy and implemented via struct returns / double return values.
FileHandle h = open_file("help.txt") onfail(err) {
// Must handle all of the cases (or have a "default"?)
NOT_FOUND:
// Can chain, and have visibility for variables in the main expression (here: h)
h = open_file("secondary_help.txt") onfail(err2) {
default:
printf("help.txt not found and secondary_help.txt errs with: %s\n", err2.msg());
}
PERMISSION_ERROR:
puts(err.msg());
}
}
Alternatíva:
- Volt az a gondolatom az error-stream hibakezelésről, ahol error stream-eket lehet definiálni
@ -313,6 +769,28 @@ Talán a megoldás valami olyasmi, hogy ne teljesen seamless integrációt csin
- runtime type info: szerintem nem kéne ilyen... érdekes, de a JonBlow-nak kell a Jai-ba és Gamedev-re is sokan sírnak érte?
- reflection: fú szerintem nem nagyon kéne - maximum compile time kiszámítható pár dolog...
## Típuskonverziók
* Szerintem ne bonyolítsuk és legyen c-style cast-olás, csak sokkal erősebben típusosan és ne legyenek implicit konverziók.
* Mellette legyen egy "asif" kulcsszó, amit reinterpret_cast műveletként használhatunk.
Lásd precedenciánál.
Szerintem ez elég tömör, de mindent leír:
int a = 0x80000000;
float f = a asif float;
int b = int(f);
Azért nem simán "as" a kulcsszó a reinterpretálásra, mert szerintem ez jobban kifejezi,
de még mindig rövidebb, mint a "reinterpret", de nem gondol az ember mást róla, mint ami!
Az alaptípusokra pedig az, hogy "konstruktor-szerűen" működnek az szerintem egyébként
is konzisztensebb, mintha elé írom C-szerűen zárójelben a cast-olást...
Mivel a nyelvben az (alap)típus mögött nem jön zárójel (konstruktor hívást jelent), ezért
ez minden további nélkül megoldható a fordítóban és mentálisan tényleg konzisztensebb...
## AOP
- Szerintem pár dolog azért hasznos lenne az aspectj-ből átvéve. Úgy, hogy nem lesz reflection, azért elég sok mindent pótolna ez!