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á.
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 one’s self or secure in one’s 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!
# Rekord VS handler: copy és move konstruktorok / assignment
Mindenképp jobbnak látom a C++ féle move szemantikát a rust-hoz képest: tehát ha én mondom meg mi történjen!
Egy érdekes (és megfontolandó) elképzelés viszont az ún. "destruktív move szemantika" ötlete, ugyanis az
nem feltétlen optimális, ahogy a C++ például csak destruálható állapotba kell tegye azt, ahonnan move-oltunk.
A destruktív move esetén a dolgot amiből move-oltunk, már nem használhatjuk. Lásd pdf itt a könyvtárban erről.
Ezt ott kifejtik miként lehetett volna C++ esetén is csinálni - de úgy, hogy továbbra is mi írjuk le mi történik!
Egy érdekes kérdés a programnyelvre nézvest, hogy vajon jobb-e, ha nem generálódik automatikusan le a
copy assignment operátor, mert ugye ha pointereket tárolsz, akkor ez az automatikusan generált dolog őőő...
Szóval hibás, hisz amikor ownershiped is van akkor elég necces, mert lemásolva akkor osztott owner lett hirtelen...
Viszont sima "hagyományos" adaton ez nem probléma, tehát:
handle Vec2f {
public:
float x;
float y;
};
^^erre kényelmes, hogy generálódik!
Lehetséges variációk - amitől a programnyelv kényelmes és "viszonylag biztonságos" is egyben:
1. Csak move generálódik, a default move az swap-olást jelent a másikkal (miint c++ esetén)
2. Generálódik copy assign meg konstruktor, de csak akkor, ha nincs pointer adattagod - ez ugye garantálja, hogy nem történnek "meglepetések"
3. Lesz struct és lesz "handle" a handle-nek van konstruktor-destruktorja, de a struct-nak nincs. A struct-nak is lehet "függvénye" és persze ott is minden public default-ban. A struct továbbá automatikusan másol. Ha a struct-ban akarok hívni malloc-ot, vagy free-t meg hasonló @resource-os dolgokat, akkor @unsafe-elnem kell - tehát ha pointer van benne, akkor az ownership így érzésem szerint általában nem nálam van egyébként sem és ugye mivel destruktor sincs, ezért a "meglepetés" egyébként sem történhet meg..... esetleg ezt struct helyett "rekord"-nake kéne nevezni, hogy semmiképp se kezdjék megpróbálni az emberek "mindenre is ezt használni" ha C/C++ felől jönnek....
Jelenleg a harmadik variáció tűnik nyerőnek a "rekord" adattal. Fontos! A tagged és egyéb union-ok SEM tudnak konstru-destruktort!
Ezzel ilyet tehát leírhatunk (a rekordnak a public a default, a handle-nak valamely privátabb dolog):
record Vec2f {
float x;
float y;
};
Megj.: Azért nem a "struct" nevet választom erre, hogy ne akarják a C/C++ felől jövők mindenre is ezt használni / erőltetni!
- Pointerekre meg van egy ownership modell, hogy a handle-khez tartoznak a pointereik. Csak ők írhatják őket (más nem) - típus szerint ők írhatják, tehát más azonos típusú handle-nek is írhatom!
- 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). Ez egyszerűen kell az alap működéshez.
- 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á...
- Nyilván ezek mind privátok: de ha valaki referenciát ad paraméternek a saját típusodból egy metódusodban, az abban lévőket eléred mert van rá ugye visibility-d úgy. Meg esetleg ugye csinálhatsz friend jellegű dolgot, vagy "owner" class-t aki eléri a pointereid. Esetleg ha egymásba ágyazott class-okat csinálhatsz, akkor ez default bekerül friend-ként, hogy a class törzsében definiált másik class alapból a kintit tekinti ownernek... Ez az "owner class" jó ötletnek tűnik - el is neveztem protectornak - és így lehetnek protected pointerek - na azokat éri el az a speciális class / handler!
- A pointereknek adhatsz értéket, növelheted, csökkentheted, szorozhatod meg a faszomsetudja még mit akarsz vele. De csak a saját pointereidre van normális esetben láthatóságod... Tehát más pointerei amit kapsz azokat nem tudod változtatni se értékadás jobb oldalában szerepeltetve "lementeni" magadnak.
- Alapból a pointer lehet null - alapból a referencia nem lehet null! Külön kell jelezni, ha lehet null egy visszaadott referencia, vagy paraméterben leírt ref - tehát ez a típus része. Mondjuk egy &ref? jellegű syntax, vagy valami ilyesmi.
^^ez szerintem majdnem mindent kezel, de nyilvánvalóan nem mindent és pont az a célom, hogy a rust-al ellentétben NE akarjak mindent safe-en tartani, csak minél többet...
asszem egyedül ezt nem kezelem így:
T& genyoka() {
vector<T> tmp;
tmp.pushback(T());
return &tmp[0];
} // obviously dangling reference
^^de mivel a referenciákat kvázi "nem tárolhatod el" ezért az ilyen problémák baromira lokálisak maradnak szerintem - nem szétszóródnak a kódban.
Természetesen ha @unsafe-nek jelölöd a függvényeid, akkor eltekinthetsz ezektől - olyankor mondjuk hozzáférsz mások pointer adattagjaihoz, használhatsz és adhatsz vissza pointer-eket és hasonlók. De ha a hívó maga nem unsafe, akkor nem tudja eltárolni a pointered, mert ugye nem tud rá változót csinálni (ha van valami var/auto jellegű típuskikövetkeztetés, akkor ott le kell állítani, ha nincs akkor eleve nem probléma, mert le se tudja írni az illető). Ugyebár természetesen az ilyen módon jelölt függvények továbbá mindenféle módon jelölt függvényt simán meg tudnak hírni - tehát pl. malloc-ot, vagy egy adatbázis kapcsolat létrehozást / elengedést is.
A @unsafe-t lehet hogy handle pointer blokkjára is engedhetek rátenni, ami azt jelentené, hogy az ő pointereit más is látja, esetleg egész dologra, amelybe lévő dolgok így öröklik lefele?
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...
A C++ esetén ugye probléma, hogy van "new", de mi eleve nem használunk ilyen dolgot.... A "new"-t amúgy is át kéne úgy nevezni, hogy "allocate-on-heap-while-being-slow-and-randomly-place-in-memory"... Mert nem kéne, hogy össze legyen kötve az allokálás fogalma az "új dolog létrehozásával" ugyebár... Ez csak az OOP miatti agymenés...
Megj.: A "generational arenas" esetleg legyen támogatott? Ez egy olyan dolog, hogy az elemekhez amit allokálsz, odakerül egy "int generation" mező prefixként. Az indexek ezt követően "fatpointer" szerűek (mondjuk 2x32bit, vagy akár 2x16) és a "generációt" is tárolják "optimistic locking" szerűen. Ha törlésre kerül egy elem, akkor a generációja (-1*) szorozva lesz: ebből látni, hogy nem ugyan az, mint amire még az indexem valid lenne, tehát már dead object. Ellenben ez a memória cella / terület újra kiadható (és nem kell hosszú free-list, mert elég párat tárolni és utána szkennelni (akár külön szálon szkennelni amikor kiadok a listáról - ez a szál nyilván thread pool-os jelleggel már létezik, csak pihen) és ilyenkor a területre beadható a ((-1*gen) + 1) új érték, ami miatt a generáció ugye megint csak nem az lesz, mint amit hivatkoznak! Ez valszeg nem kéne nyelvi elem legyen, de attól függ mennyire arénázunk.
### allokálgatás manuálisan
Szerintem talán nem is szükséges ez nyelvi elemként - elég, ha a standard library "jó filozófiával" lesz csinálva (lásd kismap)
## Context struct
Lásd:
https://www.youtube.com/watch?v=XoiFOK2m0pc
## Temporary storage
Érdekes ez a JonBlow-féle temp storage koncepció magában is - de szerintem az alapeseteink jól kezelik ezt (C++hoz képest is), csak talán
tanulni lehetne abból, hogy a Jai erre milyen kényelmi featúrákat ad...
https://jai.community/t/temporary-storage/133
https://www.youtube.com/watch?v=MeF4a75kxk0
Tehát ad vissza string-eket, meg hasonlókat mind temporary storage-en a videójában...
Így lehet referencia / ptr csak minden (érték-szemantika helyett), az owner meg scope / context függő `thread_local` lényegében.
Tehát mondjuk egy játékban minden frame-nél hívódik meg a "release-all" függvény, ami a temporary storage-t elengedi.
Az előny is, hogy egyben kerülnek elengedésre, meg azért tegyük hozzá hátrány is, a RAII/defer még mindig jobban tetszik...
## Defer
Jó kérdés, hogy legyen-e a RAII-s megoldáson kívül zig-szerű defer is. Szerintem nem kéne, csak összezavarja az embereket.
Annyiból lehet érdekes, hogy ugye a RAII-s handle behozza a copy / move szemantikát, amit a defer magában nem hoz még be,
de ígyis-úgyis érteni kell mi történik. Inkább a copy/move legyen kicsit logikusabb, mint C++ban szerintem (nem error-prone).
## "Stackvector"
Furcsa, hogy senkinek nem jutott eszébe, hogy a stack-en is lehessen vektor-t csinálni. Ezt C++ nyelven meg is lehet csinálni egy
kis kézi asm-el, meg hasonlókkal, de elég trükkösnek hangzik (és ott ez error prone, mert nem nyelvi elem). Kérdés kéne-e ilyen?
# Párhuzamosság / Multithreading
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.
- Lock-free programozáshoz legyen memory modell... Lehetőleg a C/Cpp-hez hasonló??
- Pub-sub primitívnél library szinten lehetne az ötletem amit balásznak meséltem a volatile (igen a C-s volatile!) változóról, azonos fizikai, de külön hyperthread processzoron lévő szálnál gyorsabb kommunikálásra??? Nem hiszem, hogy nyelvi elem kéne legyen, de esetleg standard lib.
De a minimálisabb követelmény / alapabb szett
Szerintem kell nyelvi szintű memory modell, az atomic jellegű dolgok és memory barrier-ek átvétele a C++ból kb. jó lesz és még
talán a volatile is maradhat a C-s jelentésében (vagyis nem java-szerűen). Emellett egy sima és egy read-write lock még jó cucc.
Jó kérdés, hogy akarunk-e valami SIMD történetet. Első körben legyen elérhető a C-re fordítás, majd ott bele gányolják ha kell.
Ilyen go-jellegű, gorutinos történet talán jó volna, esetleg pipeline és tisztán funkcionális esetre ilyen automatikus PP.
Low-prio... Őszinte leszek - igazából a memory model csak az "igazán" fontos, a többire lehet hogy elég egy pthread...
## Új ötlet
Nyelvi elem a metaprogramhoz hasonló pipeline-os és assoc-os és funkcionális tételekre + Java-féle library + memory modell + gorut
^^Ez szerintem egy jó kombó, igazából sokkal kényelmesebb, mint a rust async mágia, de elég powerful oszt csőváz.
# Standard library
- Az "igazán" standard libnek szerintem nem kell "prefix" meg "namespace".
- Legyen string típus
- stl-szerű dolgok, de pl. lehetne numberizer-alapú a sort algók interfésze (Magyarsort, meg kicsit átgondoltabb, stb. stb.)
- Felmerül, hogy akarunk-e Jai-szerű opcionális desktop standard libet: ablakozás, alap grafika, stb.
- Felemrül, hogy akarunk-e az előző ponthoz hasonlóan opcionális web standard libet: pl. rest server, uWebsocket-el, clientside bindingok a dom-on mászkálásra, stb... Talán első körben nem kéne
## Memória a standard lib esetén
Legyen a kismaphoz hasonló - tehát (template) paraméterként kapnak a handle-k malloc és free függvényeket? Vagy van erre nyelvi cucc
# Filozófia
- Suckless - amennyire csak lehet legyünk minimalisták és egyszerűek!
- Lehetőleg ne millióféleképpen lehessen valamit megoldani...
- Ne akarjunk "tökéletességet", inkább praktikusságot. Pl. A borrow checker talán tökéletesebb, de bonyolultabb érteni.
- Minimális függőségek (pl. lehetőleg ne kelljen LLVM ehhez!)
Összességében "worse-is-better" jellegű filozófia atekintetben, hogy inkább legyen az implementáció egyszerű, mint mindenre kiterjedő - és a konzisztenciához képest is fontosabb ez, mellesleg az implementációs egyszerűség fontosabb az interfész egyszerűségénél is.
# Egyéb dolgok
## Szintaxis
Jobb szeretném, ha a C-hez közelebb állna a syntax, mint pl. a zig, vagy rust csinálja. Sőt az optimális az lenne, ha bármely C kód fordulna is - amennyiben egy @unsafe-t ráteszünk az adott függvényre. Ez persze nem strict cél, hanem hogy "általában így legyen" mondjuk az már jó lenne. Tehát nem feltétlen lenne jó mindent is implementálni a C-ből, de pár alap dologban legyünk hasonlatosak szerintem.
## Bitfield-ek
Lásd C/C++ esetén:
struct s {
unsigned int a : 10;
unsigned long b : 60;
}; // 10 bitet használ az 'a', majd 60 bitet a 'b' - tehát 2*8 = 16 byte-on kell tárolni a struct-ot!
A protected itt érdekes, mert nincs öröklődés, ellenben a handle-k megadhatják kiket protektálnak (kiknek védnökei):
handle A {
protected:
int a = 10;
};
handle B {
protected:
int b = 42;
};
handle C protects A, B {
static work(A &a, B &b) {
print(a.a + b.b); // possible
}
}
Nem inheritál, csak simán ad láthatóságot - főleg akkor hasznos, ha van B adattagod, vagy egy vektorod rá, vagy dolgozol vele.
Ez segít kicsit több kontrollt elérni kompozíciónál. Esetleg ez a típus része kéne legyen?
handle C {
static work(protectee A &a, protectee B &b) {
print(a.a + b.b); // possible
}
}
Így egy "readable protected" kombó is járja akkor már ami kifele olvasható, de csak protectoroknak írható. Hasonlóan jön még
a "protected resource" és a "readable protected resource" is - de ezek igazából kombinációk / modifier-ek ilyen módon.
Ha valaki erre azt mondja - ez mekkora hülyeség! A C++ legalább a friend-nél úgy csinálja, hogy a class meg kell mondja a barátait, nem csak úgy "jelentkeznek" a barátai a semmiből.....
annak azt mondom: az örökléshez meg nem kell semmi ilyesmi, szóval eddig pontosan ez volt örökléssel, hogy aki csak szeretett volna ilyet, odaírta, hogy örököl tőled és ugye hozzáfért a protectedhez....
## Metaprogramozás
- Az implementációs közegből kifolyóan, a compiler scriptelésével - esetleg itt-ott erre valami syntax sugar, hogy ne legyen bonyi.
- A zig comptime-ra érdemes még ránézni és valami hasonlót esetleg csinálni? Azt sokan szeretik és produktívabb, mint a rust makrók.
- Valamilyen szintű generic: lehetőleg nem ducktype-os, hanem a template-ekhez közelebbi módon okosabb, de nem turing teljes
- Igazából a fent leírt "trükközés" az "AKA" kulcsszóval és "*AKA" kulcsszóval!
- 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!
- 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).
- Volt az a gondolatom az error-stream hibakezelésről, ahol error stream-eket lehet definiálni
- Ez kicsit egybemossa / együtt kezeli a logolást és hibakezelést. Konkrétan az error-ok és malfunction-ök ilyen stream-be kerülnek, ahonnal popp-olhatjuk őket - vagy ignorálhatjuk is. Ha hibát ignorálunk, akkor ebből alapesetben leállás lesz, ha malfunction-t, akkor viszont logolódik. De ignorálás helyett tehetünk is valamit - ami lehet az is, hogy kézzel leignoráljuk pl., vagy akár a sorban hagyjuk. Ez egyfajta globális hibakezelés, csak a getlasterror-jellegű dolgok helyett nem felejtődnek el a hibák - hacsak úgy nem konfiguráljuk a dolgokat. Ha "úgy" konfiguráljuk, akkor thread-local szemantikával globálisak csak - tehát nem úgy, mint a C-s esetben a nagyon-nagyon globális hibák.
Ezt elég barokkosan kidolgoztam, de az volt a bajom vele, hogy túl összetett és talán nehezen érthető. Az, hogy a hibák simán értékek, mint go-ban, vagy rust-ban az sokkal egyszerűbb - és ennek a nyelvnek az egyik előnye az egyszerűség lenne ugyebár pont!
Igazából nem cél a visszafelé kompatibilitás. Nem is nagyon lehet megmondani, hogy mi lenne a nyelv "őse" ami felé kompatibilis kéne legyen nyelvi és szintatktikai szemmel nézve...
Ami viszont jó lenne, ha egyből tudnánk fordítani C/C++ kódot a zig-hez hasonlóan. Ez a zig-nek azért brutális előnye! Viszont vannak ezzel kapcsolatban azért komoly nehézségek, ezért lehet hogy sokkal járhatóbb út, ha ezt a lépésenkénti átállást inkább úgy támogatjuk, hogy az egyik referencia implementációja a fordítónak C/C++ kódot fordít! Azért így írom, hogy C "slash" C++, mert szép dolog C-re fordulni (és onnantól minden mikrovezérlőn is elfutni, meg mindenhol, ahol van C), de a C++-ra fordulásnak akkor van előnye, ha C++ interop-ot szeretnénk és például hívni akarunk onnan osztályokhoz tartozó metódusokat, vagy hasonlókat.
Az itt a probléma, hogy ha megengedjük, hogy C++ kódokat hívhassunk (azáltal, hogy C++ra fordulni is tudunk), akkor vajon a nem C++-ra forduló fordító backend ezt hogyan fogja kivitelezni? Az extern "C" jellegű dolognál nincs annyira ez a probléma, de praktikusan ezt megoldani mégis elég hasznosnak tűnik, mert akkor legacy kódot tudnak erre a nyelvre emelni a zig-hez hasonlóan.
Talán a megoldás valami olyasmi, hogy ne teljesen seamless integrációt csináljunk, hanem "csak majdnem".
^^Azért a nyelv saját magával legyen már visszafelé kompatibilis majd... szóval nem akarok python-szerűt csinálni majd...
## Típusrendszer
- Alapjában véve erősebben lenne típusos, mint a C/C++. Tehát pl. nem engedünk int-ből double-be csak úgy adatot tenni meg ilyenek, kevesebb lenne az ilyen mágia is...
- Jó kérdés, hogy legyen-e típus-kikövetkeztetés, meg ilyen modern cuccok amit a "nyelvek típusrendszere" kurzuson tanultam még egyetemen és sok modern nyelvben van.
- A referenciánál az, hogy lehet-e null az a típus része!
- Öröklődés alapú polimorfizmus: nincs és nem is akarok!
- Szerintem NEM duck-typing-al kéne generic-et csinálni, vagy template-et...
- 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...
- 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!
- Igazából az erős metaprogramozás miatt ez szerintem megvalósítható ilyen @Before és @After dolgokkal meg @MyAOP jelölőkkel.
- Megj.: Talán egyszerűen a @-os szavak koncenció szerint dolgozhatnak a mögöttük lévő cuccoson / talán ez nem is külön effort,
de úgy érzem valahogy jobb lenne ezt külön nyelvi szintre emelni? Tényleg nem biztos, mert elvileg ez megírható anélkül..
## Plugin / modul architektúra
- Lehet hogy jó volna nyelvi szinten rendesen támogatni? Vagy akár csak library szinten? Lásd videót ahol TsodingDaily hot code replace-t csinál C-ben. A rákövetkező videó is jó, ahol X-makrókkal csinálja meg hogy olvashatóbb legyen (amikor fordítunk dolgokat akkor is jó volna ezt is ismerni - addigra valszeg elfelejtem és nézhetem újra).
- Fordítási idejű plugin/modulozás viszont tök hasznos volna! Az talán nyelvi szinten is!
- importálás talán java szerűen, csak annál is egyszerűbben. Majd megnézem a go hogyan csinálja pl. A lényeg, hogy include helyett jó lenne ha a fordító tudná hogy vannak modulok és azok mit exportálnak, onnan mit importálunk. A C/C++ modulokat is meg kéne nézzem hogyan működnek - főleg ha kéne az interop (vagy akár az erre fordító fordító backkend..)
- Fordítási idejű dependency injection is lehetne.
Amit nem kéne: Nem kéne, hogy az egész világ annyira dinamikus és szálkezelős legyen, mint az OSGi-al java-ban. De pl. onnan a product-modul-komponens felosztást jó lenne megcsinálni. A modul-t és namespace-t összekötném (mint ahogy java-ban OSGi modulokkal össze is kötöttem jellemzően?) - esetleg a runtime esetet úgy kezelve, hogy a namespace megjelölhető modulnak, de így ugye lehetnek sub-namespace-jei.
Azt, hogy hol keresse a kódot jó kérdés hogy java-szerűen akarom-e, vagy C/C++ szerűen... tehát hogy a namespace szabályok írják-e le, vagy sem. Mindkettőnek van előnye ugyebár... és hátránya is...
## Operator (és egyéb) overloading
- Magam is megosztott vagyok, hogy legyen-e. Valamennyire azért kívánatos pl. egy vektor típusnál hogy simán tömb-szerűen használható.
- Azoknak is igaza van, hogy nehezebb követni mi történik: mekkora a költsége egy műveletnek...
- Igazából a múltkor a "dinamikus programozás és algebrai csigamátrix optimalizálás" videómban nagyon kényelmes volt! De talán külön kéne "rendes" 2D tömb nyelvi (vagy meta?) szinten és csőváz? Vagy esetleg ha már overload-al van csinálva, akkor megoldhatnánk, hogy matrix[x][y] legyen? Egyébként szerintem ez C++ esetén is megoldható... vissza kellet volna adnom egy objektumot az offset értékkel és az eredeti tömb pointerrel - és ezen objektumnak lett volna szintén operátor overload-ja, ami a végső számítást végzi...
## Fordítás
Mint említettük korábban a "strukturált formában tárolt programszöveges" ötlettel lenne ehhez referencia implementáció - sőt ha metaprogramozásnak azt tartjuk meg, akkor valószínű, hogy más implementációk is azt kell használják - ez egy érdekes kérdést vet fel, mert lehet hogy gyorsabb kód fordítás lenne elérhető, ha nem azzal metaprogramoznánk, hanem csak arra átírnánk a kódot - tehát ha kikötjük, hogy nem lehet ilyen metaprogramozás (forth-szerű szó definiálás), hanem mi magunk saját metaprogramozást alakítunk ki, amit arra fordítunk az első lépésben le... Ez lehetővé tenné, hogy mások, más módon is implementálhassák a nyelvet - ami vagy jó, vagy nem jó...
Én arra gondoltam, hogy hasznos lenne olyan, ami (emberileg olvasható) C vagy C++ kódra fordít bináris helyett - de emellé egy "rendes" fordítót is érdemes volna írni. Először is, ezzel nagyon jó optimalizációkat kapunk "ingyen", másodszor az interop-nak nagyon segít.
Viszont ugye a saját megoldás is nagyon fontos, de egyben ott a strukturált formás ötlet, ami erre lehetőséget is ad. Milyen target-eket kéne viszont supportálni? Szerintem a legjobb az volna, ha direkt webassembly (wasm) targetünk lenne! Emellé esetleg még egy x86-os target mondjuk linux-ra. Onnan a community kihozhatja a többit ha nagyon kell. A wasm lehetne optimalizálni is képes compiler, ami viszont gyorsan is fordít (jai-szerűen gyorsan?), de ha nagyon-nagyon release-t akarunk, akkor ott lenne a C/C++ kimenet.
A C/C++ kimenettel az a nehéz, hogy egyeztetni kell a szemantikáinkat - de mivel nem valami extra bonyolult programnyelvet csinálunk, ez talán ugye nem is olyan nehéz ;-)
- Természetesen érdemes elgondolkozni, hogy eleve adjunk-e build-system-et, vagy ne. Ez egy érdekes kérdés. Ha nem adunk, akkor a modul lookup stb. is bonyolódhat pl. de könnyebb beilleszteni a BASED programokat meglévő build rendszerbe amikor interop van (pl. makefile) és ugye kevesebb effort is megcsinálni. De inkább arra hajlok, hogy mi magunk, a fordítóval build-eljünk. Ez mondjuk még a "strukturált..." megvalósításától is függ, hogy miként is lenne jobb... Szóval valamennyire nyitott kérdés is...
Nem igazán támogatom, bár ha véletlen sikeres nyelv volna, nehéz lenne megoldani, hogy mások ne csináljanak hozzá. De a legjobb az volna, ha first-class támogatás lenne a manuális csomag / modul kezelésnek, a sima bemásolgatósnak...
## OS support
Linux-first, onnan aki szeretné implementálja át portolással más OS-re szerintem...
Az SLC jelentése kettős: SuckLessCode és SuckLessCompiler. Egy általános fordító / interpreter / transpiler / metaprogramozás tool.
Alapjában véve egy forth-szerű, interpretált általános célú nyelv önmaga is, de speciális nyelvi elemekkel compiler íráshoz!
Az címet 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.
Viszont ha ezt megcsinálom, akkor ezt az itt leírt nyelvet, ezzel implementálva alapból van metaprogramozásunk - méghozzá mindennél erősebb. De ugye nem feltétlenül kényelmes is... Pl. a jai, vagy a zig comptime lehet hogy sokkal kényelemsebb.
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). De a gyakori state-machine használat miatt változói és adattagjai is vannak a szavaknak, továbbá zárójelezésük!
## Fordítási időben / interpretálási időben futó szavak és példák
#: int
process_varname
declare_int_var
#
int alma;
Macerásabb:
struct MyAlma {
};
var MyAlma alma; // ??? var ??? több menetessel megoldható, hogy ne kelljen var, mert a típusok
Mivel a szóvégek nem mindenhol "jó" helyen vannak, ezek átírandók:
set x=1+ a(1, b(3, 4));
Ilyenre:
set x = 1 + a(1 b(3 4));
Ha egy programnyelvnek saját "zárójelezése" van, ami esetleg még kontextusfüggő is, akkor ilyesmit lehet:
- a szóhoz tartozó olvasása a stack-re tehát csak a neve, illetve a kukacos neve
- írása meg a zárójelbe tett dolgot írja bele - kivéve hogy ha ott "pont" áll akkor a stack tetejét írja bele és csőváz
- csak 4 byte-os int van és csőváz... Ha valaki mást akar megoldja "handle"-kkel majd meg float-ra lesz gyakorlatilag reinterpret cast-olgatás vagyis a művelet határozza meg, hogy float-ként dolgozol-e a négy byte-al, de ez talán nem baj, ha extension, mert a fordítóba nem nagyon kellenek float-ok szerintem.....
Egy adattagokat(változókat) is tartalmazó szó és példa a "lokális" változó-elérésre...