Üdv a második tematikus hónapunkban, a Mechanical Marchben. Ebben a hónapban a rendszernyelvekre összpontosítunk, azokra, amelyek gépi kódra fordulnak le.
Ez részben bejegyzés, részben a Mechanical March videó átirata. Adok egy gyors bevezetőt a hónaphoz, aztán egy kicsit megnézzük a rendszernyelveket, a történelmi fejlődésüket, a gépi kódra fordítás előnyeit és hátrányait, valamint röviden mindegyik kiemelt nyelvet. Ismét velem van Erik, aki a második felében fog a legtöbbet beszélni. De először én kezdem a gyakorlati információkkal.
Tehát először is a hónap kiemelt nyelvei. Ezek a C, C++, D, Go, Nim, Rust, V és Zig. Ahhoz, hogy megkapd a Mechanical March jelvényt, öt feladatot kell teljesítened valamelyik ilyen nyelvben. A Go-kurzusunk tanterve az egyik legjobb az Exercism-ön, szóval igazán ajánlom, hogy próbáld ki. Az Exercism-nél mi is nagy rajongói vagyunk a Nimnek, mivel viszonylag egyszerű nyelv a kezdéshez és nagyon könnyű benne írni, szóval mindenképp ajánlom, hogy azt is próbáld ki.
Öt kiemelt feladatot is kipróbálhatsz:
- linked-list vagy simple-linked-list (nyelvtől függően): memóriafoglalás/felszabadítás, mutatók
- secret-handshake: bitműveletek
- pangram: for ciklusok, stringek és karakterek
- sieve: tömbök, for ciklusok
- binary-search: tömbök, ciklusok
Van egy új jelvény is, amit a néhány nappal ezelőtti frissítési videóban jelentettem be, és az öt kiemelt feladat teljesítéséért jár a tematikus nyelvekben. Tehát hogy megkapd azt a jelvényt, az év során valamikor mindegyik ilyen nyelvben teljesítened kell a feladatokat.
Sok izgalmas dolog is készülődik: interjúk a Go Core csapat néhány tagjával, és remélhetőleg a Rustból és néhány más nyelvből is. A hónap során rengeteg élő közvetítés is lesz. És hamarosan érkezik némi Mechanical March ajándéktárgy is!
Szóval ássunk bele egy kicsit jobban a technikai oldalba.
Mire használják ezeket a nyelveket?
Nos, az Exercism-ön mindenhol használjuk őket. A CLI-nk Go-ban íródott, a configlet nevű belső kurzuskezelő eszközünk Nimben, és a kulcsfontosságú könyvtár, ami megszámolja a megoldásaidban a kódsorokat, Rustban íródott. Erik, miért választottuk ezeket a nyelveket ezekhez az eszközökhöz?
CLI:
- Szerintem azért használtuk a Go-t, mert Katrina azzal volt a legjobban tisztában.
- A Go remek az ilyen kisebb parancssori eszközökhöz.
- A Go-kód viszonylag egyszerű, ami megkönnyíti a közreműködést
- A Go binárisok könnyen telepíthetők, mivel nem igényelnek futtatókörnyezetet
- A Go jól kezeli a keresztfordítást
Nim
- A Nimnek nagyjából ugyanazok az előnyei, mint a Go-nak
- Nem volt sok olyan ember, aki ismerte volna a Go-t és karban tudta volna tartani a CLI-t, ezért váltottunk a Nimre
Milyen más helyeken számítanál arra, hogy ezeket a nyelveket használják?
- Bárhol, ahol a teljesítmény fontos (illesztőprogramok, játékok, operációs rendszerek, buildrendszerek/fordítók)
- Bárhol, ahol korlátozottak az erőforrások (pl. beágyazott szoftverek)
- Bármi, aminek nagyon hordozhatónak kell lennie, azaz: sok különböző platformon fusson
Mi a gépi kód?
Ahogy korábban mondtam, a Mechanical March nyelveit az különbözteti meg, hogy gépi kódra fordulnak. El tudnád magyarázni egy kicsit, mi a gépi kód, és ezzel szemben mi a bájtkód?
- A gépi kód olyan kód, ami közvetlenül futni tud a gépen.
- A bájtkód ezzel szemben más kódot igényel ahhoz, hogy a bájtkódot gépi kódra értelmezze/fordítsa. Ezért a bájtkód végrehajtás előtt egy közbenső lépést igényel.
Mik a gépi kód előnyei és hátrányai a bájtkóddal szemben?
Előnyök:
- Gyorsabb indulás (nincs JIT-fordítási lépés)
- Kisebb memóriaigény (nem töltődik be futtatókörnyezet, nincs bájtkód a memóriában, remek beágyazott rendszerekhez)
- A célgépnek nem kell futtatókörnyezetet telepítenie (fontos a docker konténerek kicsiben tartásához)
Hátrányok:
- Nem hordozható. A bájtkód hordozható, de a lefordított gépi kód platformspecifikus
- Nem lehet olyan fejlett optimalizálásokat végezni, mint a profilvezérelt optimalizálás (annak meghatározása, hogyan érdemes a legjobban lefordítani a (bájt)kódot, miután egy ideig futott)
Megjegyzés: hibrid megközelítések is lehetségesek, ahol a nyelv bájtkódra fordul, majd egy másik eszközzel fordítja le azt a bájtkódot gépi kódra.
A rendszerprogramozás fejlődése
Oké. Nézzük meg egy kicsit a hónap néhány nyelvének fejlődését. Először is kezdjük a legelején, a C-vel, és azzal, hogyan fejlődött ki belőle a C++. Mesélj egy kicsit ezekről a nyelvekről
A C egy nagyon alacsony szintű nyelv. Úgy érzed, mintha csak kicsit lennél a gépi kód felett. Ez nagyon erőssé és jól optimalizálhatóvá teszi, de kissé hajlamossá is a bugokra (pl. null pointer kivételek és puffertúlcsordulások). A memóriakezelés teljesen manuális, így a programozó felelőssége, ami potenciálisan bugokhoz és/vagy memóriaszivárgásokhoz vezethet. A C++ olyan, mint a C, de támogatja az objektumorientált programozást. Még mindig meglehetősen alacsony szintű, és manuális memóriakezelést igényel. A C és a C++ is lehetővé teszi, hogy inline assemblyt (ASM) írj!
És mi a helyzet az újabb rendszernyelvekkel? Hogyan fejlődtek?
Minden modern rendszerprogramozási nyelv támogatja az automatikus memóriakezelést, akár referenciaszámlálással, szemétgyűjtővel vagy valamilyen más mechanizmussal.
A korai rendszerprogramozási nyelvek mind támogatják a null pointereket (amit Tony Hoare az „egymilliárd dolláros tévedésének” nevezett). Ezek hírhedtek arról, hogy futásidejű hibákhoz és sebezhetőségekhez vezetnek. Sok modern nyelv megszabadul a null-tól, vagy legalábbis erőfeszítést igényel a használata.
Egy másik változás, hogy a módosítható értékek alapértelmezettségétől elmozdultak az alapértelmezés szerint megváltoztathatatlan értékek felé. Pl. a Rustban és a VLangban is alapértelmezés szerint megváltoztathatatlanok az értékek, és külön kell kérni a módosíthatóságot.
Minden újabb nyelv támogatja a C-vel (vagy C++-szal) való együttműködést, mivel rengeteg kód íródott ezeken a nyelveken.
Egy másik érdekesség, hogy néhány újabb nyelv nem közvetlenül fordít gépi kódra, hanem más eszközöket használ erre. Pl. a Rust és a Zig az LLVM-et használja, míg a Nim sokféle fordító használatát teszi lehetővé. Ezt nevezik transzpilálásnak.
És mi a helyzet az olyan dolgokkal, mint a makrók és a metaprogramozás?
Érdekes megosztottság van a makrók/metaprogramozás terén. A C/C++ makrói erősek, de némileg kétes hírnevük van, mert nehéz velük dolgozni. A Rust, a Nim és a D mind erős metaprogramozást kínál, de sokkal szebb módon. Ezzel szemben a VLang és a Zig kifejezetten a nyelv jellemzőjeként említi, hogy nincsenek makrók, a Go pedig más megközelítést alkalmaz a go generate-nel.
A rendszernyelvekről az a hír járja, hogy meglehetősen alacsony szintűek. Ez még mindig igaz?
Az újabb nyelvek mind magasabb absztrakciós szinten működnek, mint a C/C++. Pl. a Rust, a D és a Nim lehetővé tesz egy nagyon funkcionális kódírási módot is. A Nimben és a D-ben még a „tiszta” függvények fogalma is megvan.
Bevezetés a hónap nyelveibe
Jó lenne egyenként megnézni a nyelveket. Mindegyik nyelv hasonlít: mind erősen és statikusan típusos. De nézzük meg, miben térnek el. Kezdjük a C-vel?
C
- Dennis Ritchie fejlesztette
- Az egyik legrégebbi, és valószínűleg a világ legtöbbet használt nyelve
- Rengeteg szoftver íródott C-ben, például a Unix és a Linux
- Nagyon nagy hatású (gondolj csak arra, hogy léteznek C-szerű nyelvek)
- Manuális memóriakezelés
- Nagyon jó teljesítményű (közel a hardverhez)
- Bárhol fut
- Ideális beágyazott rendszerekhez
- Meglehetősen kicsi nyelv
C++
- Bjarne Stroustrup fejlesztette
- A C utódja, de hozzáadott objektumorientáltsággal (C osztályokkal)
- Segített népszerűsíteni az objektumorientált programozást
- Több magas szintű nyelvi funkció, mint a C-ben
- Támogatja a generikus programozást sablonokon keresztül
- Modultámogatás névtereken keresztül
- Sok játék(motor) íródott C++-ban, és a Windows nagy része is
- Manuális memóriakezelés
- Még mindig fejlődik, rendszeresen kerülnek bele új funkciók (nagy specifikáció)
D
- Walter Bright fejlesztette, később Andrei Alexandrescu is csatlakozott
- Eredetileg újramérnökölt C++-nak szánták (tanulva annak „hibáiból”), de sok más nyelvből is merít inspirációt
- Több paradigmát támogat: imperatív/objektumorientált/funkcionális programozást
- Könnyű együttműködés a C/C++-szal
- Egységes függvényhívási szintaxis
- Fordítási idejű függvénykiértékelés (pl. reguláris kifejezés állapotgépének generálása fordítási időben)
- Támogatja a funkcionális programozást és a „tiszta” függvényeket
- Sok biztonsági funkció
- Memóriabiztonság @safe segítségével
- Szerződések (elő-/utófeltételek, invariánsok)
- Tiszta függvények
- Fókusz az egységteszteken, a tesztek a tesztelt forráskód mellett vannak (ez kivételt igényelt az Exercism weboldalán :))
- Unicode
Rust
- A Mozilla Research alkalmazottja, Graydon Hoare fejlesztette, majd a Mozilla hivatalosan is felkarolta, és ma a Rust Foundation része
- Több paradigmát támogat: OO (de véleményvezérelt módon, pl. nincs öröklődés), imperatív és funkcionális programozást (Option/Result típusok, mintaillesztés)
- Sok új eszköz íródott Rustban (pl. SWC, de a Gleam is, és a Linux kernel második támogatott nyelve; mi is rá támaszkodunk a kódszámlálóhoz)
- Fókusz a megbízhatóságon és a teljesítményen
- Az LLVM-re épül
- A StackOverflow felmérésén az elmúlt 7 évben a legkedveltebb nyelv
- Gyors, részben a minimális mag és standard könyvtár miatt
- Biztonságos, mind memóriabiztonság, mind szálbiztonság terén, tulajdonlás és élettartamok révén, alapértelmezés szerint megváltoztathatatlan
- Erős típusrendszer, ami sok bugot elkap fordítási időben (különösen a memóriával kapcsolatosakat). A fordító igazán hasznos hibaüzeneteket ad
- Minden benne van: fordító, build eszköz, formázó, csomagkezelő, IDE-integrációk
- Remek dokumentáció (még egy hatalmas dokumentum is van arról, hogyan működik a fordító)
- Hordozható: egyetlen statikus binárisra fordul, és nem igényel telepített futtatókörnyezetet
- Könnyű együttműködés a C kóddal
- Nulla költségű absztrakciók
- Félelem nélküli párhuzamosság
- Makrók
Nim
- Andreas Rumpf fejlesztette (eredetileg Nimrod volt a neve)
- A szintaxisa a Pythonból merít ihletet
- Több paradigmát támogat
- Az Exercism-ön a configletben használjuk
- Kiváló teljesítmény: nulla többletköltségű iterátorok, az értékalapú típusok veremallokációjának előnyben részesítése
- Modern, kifejező típusrendszer: típuskövetkeztetés, tuple-ök, generikusok, összegtípusok, async/await
- Szemétgyűjtés, de determinisztikus memóriakezelés támogatásával (több memóriakezelési lehetőség)
- Fordítási idejű kódfuttatás
- Egységes hívási szintaxis
- Makrók: könnyű bővíteni a nyelvet
- Hatásrendszer: mellékhatások kódolása a típusrendszerben
Go
- Robert Griesemer, Rob Pike és Ken Thompson fejlesztette a Google-nél
- Többnyire imperatív/procedurális, objektumorientált jellegű megközelítést támogat (de öröklődés nélkül)
- Nagy projektekben használják, mint a Docker vagy a Kubernetes. Remek backendekhez és CLI-khez is (pl. esbuild)
- Célja, hogy elég egyszerű legyen ahhoz, hogy fejben tartsd (kevés szintaxis)
- Memóriabiztonság szemétgyűjtővel
- Gyors: gyors fordítás, gyors tesztelés és gyors futásidő. Beépített támogatás benchmarkok írásához!
- Véleményvezérelt: sok erőfeszítés irányul a Go-kód stílusának befolyásolására: kevés szintaxis, a kód formázása a go fmt-tal, eszközök a kód idiomatikus használatának ellenőrzésére, hibát jelez olyan dolgokra, amik más nyelvekben csak figyelmeztetések (pl. nem használt változók), dokumentáció, amely felsorolja a követendő Go-idómákat
- Könnyű típusrendszer, ami nagyon rugalmassá teszi a Go-t (támogatja a típuskövetkeztetést)
- Hordozható: egyetlen statikus binárisra fordul, és nem igényel telepített futtatókörnyezetet. Könnyű keresztfordítás. Párhuzamosság goroutine-okkal (könnyűsúlyúak) és kommunikáció csatornákon keresztül
- Strukturális típusozás interfészeken keresztül (hasonló a duck typinghoz, de statikusan ellenőrzött)
- Hibakezelés: a nyelv arra ösztönöz, hogy ellenőrizd és kezeld a hibákat
VLang
- Alexander Medvednikov és Delyan Angelov fejlesztette
- A Goból merít ihletet:
- Ugyanaz a „csak egyféleképpen csináljuk a dolgokat” stratégia
- Ugyanaz a „minimális szintaxis” stratégia
- Korutinok
- Eltér a Go-tól:
- Nincs nil/null, hanem result típust használ
- Alapértelmezés szerint megváltoztathatatlan
- Összegtípusok (funkcionális)
- String-interpoláció
- Kisebb futásidejű környezet/binárisok
- Mintaillesztés
- Azon dolgoznak, hogy a GC opcionális legyen (autofree)
- Nulla költségű C-együttműködés
- Dokumentáció generálása a kódból
- Gyors fordító, ami kevés memóriát használ
Zig
- Andrew Kelley fejlesztette
- Meglehetősen kevés szintaxis (500 soros PEG nyelvtani fájl)
- Fordítási idejű kódfuttatás és reflexió
- Célja, hogy „nyilvánvaló” legyen: nincs rejtett vezérlési folyam, allokáció vagy makrók/metaprogramozás
- Manuális memóriaallokáció
- Különböző allokátorokat támogat
- A standard könyvtár azon függvényei, amelyek allokálnak, allokátor paramétert kapnak
- A tesztkeretrendszer képes felismerni a memóriaszivárgásokat
- Biztonság:
- A hibák értékek, és kezelni kell őket
- Nincs null, opcionális típust használ
- A tesztek ugyanabba a fájlba írhatók, mint a forráskód (mint a D-ben)
- Az LLVM-et használja backendként
- Képes C/C++ kódot fordítani
- Könnyű C-együttműködés
- Könnyű keresztfordítás
Összegzés
Itt most abbahagyjuk, mert gondolom, mindenkinek eléggé tele van már a feje ezen a ponton.
Remélhetőleg ez egy hasznos és szórakoztató bevezető volt a hónap kiemelt nyelveihez. Remélem, rengeteg örömöd telik majd ezeknek a nyelveknek a felfedezésében. Eriket és engem is nagyon érdekelne, melyikeket választod, és hogy tetszenek, szóval mindenképp írj a hozzászólásokban vagy a fórumon!
Köszönjük a figyelmet!