A D szabványos könyvtára a Phobos nevet viseli. Filozófiája a következő:
Ezeken kívül még számos hasznos könyvtár található. A teljesség igénye nélkül: XML feldolgozás, reguláris kifejezések, szinkronizáció, socket-ek, algoritmusok, konténerek, véletlenszámok generálása, dátumok kezelése, stb.
A D programozási nyelv, bár több programozási paradigmát is támogat, egy imperatív programozási nyelv. Ez azt jelenti, hogy a programkódban azt definiáljuk, hogy a számítógép milyen utasításokat hajtson végre, és ezen utasítások végrehajtása oldja meg a programozási feladatunkat. A funkcionális programozási nyelvek ezzel szemben a deklaratív nyelvekhez tartoznak: programjainkat nem utasítások sorozataként, hanem egy matematikai stílusú kifejezésként definiáljuk. Talán kissé túlzóan fogalmazva: az imperatív nyelvekben a programozási feladat megoldását, a deklaratív nyelvekben a specifikációját írjuk le.
Mivel a D nyelv nem deklaratív, hanem imperatív programozási nyelv, nem írhatunk benne egészen funkcionális programot. Viszont törekedhetünk arra, hogy programjainkban felhasználjuk a funkcionális programozás eszközeit -- ha az adott részfeladathoz jobban illik ez a stílus.
A funkcionális programozás egy fő jellemzője, hogy a függvények ugyanolyan típusok, mint a primitív típusok vagy rekordok: függvényeink kaphatnak és adhatnak vissza függvény típusú paramétereket. Az ilyen függvényeket magasabbrendű függvényeknek nevezzük. A D nyelvben kényelmesen használhatunk magasabbrendű függvényeket: a függvénymutatók szintaxisa jóval barátságosabb, mint a C, C++ nyelvekben. Emellett használhatunk függvényliterálokat és lambda kifejezéseket is. Például egy rendezést végző függvénynek paraméterben adhatjuk át a rendezést biztosító függvényt, így általánosabb lesz a rendező eljárásunk:
Jellemző még a funkcionális programozási nyelvekre, hogy nincsenek destruktív felülíró műveletek. Ez a D nyelvhez már jóval kevésbé illik, de a következményeket már egy imperatív nyelvben is alkalmazhatjuk. Egy tisztán funkcionális programozási nyelvben az adataink nem módosíthatók. Ezt a D nyelvben az immutable kulcsszóval érhetjük el. Ennek előnye a hivatkozási helyfüggetlenség: ha programunk két pontján szerepel ugyanaz a kifejezés, mindkét előfordulásnak ugyanaz a szemantikája. Ez az imperatív nyelvekben nincs így, hiszen az adott kifejezés szemantikája függhet mellékhatásoktól, globális állapottól, módosítható változóktól. Hasonló koncepció a tiszta függvények: olyan függvény, melyet ha kétszer ugyanazokkal a paraméterekkel hívjuk meg, az eredmény azonos.
A D nyelv biztosít néhány eszközt funkcionális stílusú programozáshoz, de az ezekkel írt függvényeket továbbra is csak imperatív stílusban tudjuk használni. A funkcionális programozásra jellemző, hogy az egyszerű, primitív függvényekből, ún. kombinátorok segítségével építjük fel programjainkat. A D szabványos könyvtár biztosít néhány ilyen kombinátort, ezekkel már akár funkcionális módon is konstruálhatunk programokat. Ezen eszközök az std.functional modulban találhatók.
A parciális függvényalkalmazás, másként curryzés azt jelenti, hogy egy többparaméteres függvényt csak egy paraméterre alkalmazunk. Ennek az eredménye egy eggyel kevesebb paraméterszámú függvény. Szükségünk lehet rá magasabbrendű függvények használatakor, amikor egy függvényt úgy szeretnénk átadni, hogy egy vagy több paraméterét már az átadáskor rögzítjük. A parciális alkalmazást a curry függvénnyel végezhetjük el.
Az alábbi példában definiálunk egy függvényt, ami ellenőrzi, hogy két szám szomszédja-e egymásnak. Ezt a függvényt parciálisan alkalmazzuk egy célszámra, majd egy tömbben megszámoljuk az így kapott predikátumot kielégítő elemeket. A parciális alkalmazás lambda függvénnyel és a curry függvénnyel is látható.
A gyakori curryzés miatt a funkcionális stílusban írt függvényeknél a leggyakrabban rögzített paramétert tesszük előre.
Funkcionális programok esetében gyakran használunk tuple-öket, rendezett n-eseket. A D szabvány könyvtárban ezt a típust a std.typecons modulban találjuk. Az adjoin függvény segítségével több függvényt össze tudunk kapcsolni oly módon, hogy az eredmény az eredeti függvényeket alkalmazza egy rendezett n-es megfelelő komponenseire.
A függvénykompozíció egy lehetőség arra, hogy egyszerű függvényekből egy összetettet építsünk fel. A szabvány könyvtár compose függvénye jobbról balra, a pipe függvény balról jobbra végzi el a kompozíciót.
Az alábbi példában egy szöveget szavakra bontunk, a szavakat számmá alakítjuk, majd mindegyiket duplájára növeljük. Láthatunk egy imperatív és két funkcionális példát a két kombinátorral.
A funkcionális programozásban gyakran használunk rekurzív függvényeket. A hatékonyság érdekében érdemes figyelni arra, hogy a rekurzió során egy értékre csak egyszer számoljuk ki a függvényértéket. Ez elérhetjük úgy, hogy a már kiszámolt értékeket egy hasítótáblában tároljuk, és csak akkor indítunk rekurzív hívást, ha a szükséges értéket még nem számoltuk ki. A szabvány könyvtár memoize függvénye ezt a mechanizmust teszi egyszerűen használhatóvá.
A példában a Fibonacci függvényt számítjuk ki. Az értékek tárolása nélkül a kiszámítandó értékek száma exponenciálisan nőne a rekurziós mélység függvényében.
A reguláris kifejezés egy domain specifikus nyelvnek számít, gyakran csak regex-ként hivatkozunk rájuk. A D nyelvben a reguláris kifejezések használatához az std.regex csomag importálása szükséges. Ennek segítségével könnyen validálhatunk felhasználói bemetetet, és dolgozhatunk fel szöveges fájlokat.
A szöveges feldolgozás napi rutinnak számít a legtöbb alkalmazás számára ilyen vagy olyan formában. Éppen ezért a legtöbb nyelv bevezet stringek manipulálására alkalmas könyvtárbeli műveleteket. A D nyelv sem marad le, az std.string és std.algorithm csomagban vezet be rengeteg string manipulálására alkalmas műveletet, azonban ezek még nem elegendőek minden szöveges feldolgozással kapcsolatos feladatra, itt jönnek képbe a reguláris kifejezések, melyek ezektől sokkal rugalmasabb lehetőségeket nyújtanak. Sok nyelvben használhatóak reguláris kifejezések is, azonban a D nyelvben nem csak a reguláris kifejezések teljes tárháza van jelen, hanem még a hatékony használatuk is szem előtt van tartva, a készített program a lehető leggyorsabban végezi el az alkalmazásukat, kitalálva a lehető leggyorsabb algoritmust az adott reguláris kifejezésre, sőt még fordítási időben is készíthető reguláris kifejezést elvégző algoritmus, ha fordítási időben ismert a reguláris kifejezés.
Fontos, hogy ne bonyolítsuk túl a reguláris kifejezéseket, és ne használjuk arra, amire nem való. Például ha a bemenetnek 10 és 1000 közötti értéknek kell lennie, akkor azt már ne reguláris kifejezésekkel szorítsuk meg, reguláris kifejezésekkel csak a számot határozzuk meg, ezen megszorítást pedig a kapott számon ellenőrizzük le egy megfelelő feltételes vizsgálattal.
A következő példa ellenőrzi, hogy a program első argumentuma egy nemzetközi formátumban megadott telefonszám-e.
Már ebben a példában is rengeteg minden szerepel. Érdemes regexnél az r előtagot beírni a reguláris kifejezés stringje elé, vagy a backtick karaktert használni, hogy ne legyenek vezérlő karaktereket használva, azaz ha \+ -ot szeretnénk írni, akkor ”\\+” helyett írhatunk r”\+” vagy `\+` stringet. match függvény, segítségével tudjuk megvizsgálni, hogy az első paraméterként kapott stringre illeszthető-e a második paraméterben adott reguláris kifejezés. Ha speciális regex karaktert, szeretnénk karakterként használni, például +, *, (, ), [, ], akkor a \ karaktert kell eléírni. Amennyiben nincs sokszor felhasználva a reguláris kifejezés, akkor egy egyszerű stringben is megadhatjuk, nem kell külön regex objektumot létrehoznunk, azonban ha sokszor is felhasználjuk ugyanazt a reguláris kifejezést, akkor érdemes lenne eltárolni egy regex objektumban, így nem kell minden illesztésnél felépíteni.
Az előző példát folytatva, hasznos lehet, ha meg tudnánk határozni az ország hívókódját, vagy ha a teljes számot szeretnénk lekérni, akkor ezt is megtehetjük. A match hívás eredménye egy objektum, melyet le tudunk menteni változóba. Ebből a változóból lekérhetjük az egyes csoportokra kapott illesztéseket. A regexen belül zárójelekbe helyezett részek felelnek meg egy csoportnak. A lenti kódban az m objektumba mentjük le az illesztés eredményét, az m.captures[0] a teljes kifejezésre illesztett szöveget, az m.captures[1] az első csoportra illesztett, az m.captures[i] az i-dik csoportra illesztett szöveget adja vissza.
Gyakorta szükséges az összes adott reguláris kifejezésre illeszkedő szöveg megkeresése. A következő példakód az összes csak fehér szóközt tartalmazó sort kiszűri, és csak a többit írja ki. D nyelvben nincs kifejezés a bemenetben történő soronkénti keresésre, mint egy-két más nyelvben, de az összes találaton viszont végig tudunk iterálni. Ha a találatot az m objektumba mentjük le, akkor az m.hit ugyanazt az eredmény adja, mint az m.captures[0], azaz a teljes reguláris kifejezésre illesztett szöveget adja vissza.
Fenti foreach ciklus működése: match eredménye egy intervallum, melynek elemei találatok (Captures példány), a foreach m változója ezen találatokat járja be. Nagyon fontos a ”gm” flagek használata. a g azt mondja, hogy a szövegben (jelenleg bufferben) több találatot is keressen, az m pedig azt, hogy a szövegben (vagy bufferben) a sortöréseket sorelválasztóként kezelje, és a ^, $ vezérlő karakterek ne csak a szöveg elejére és végére, hanem a sorelválasztójelekre is illeszkedjenek. Egy hasonló példa, mely a bemeneten kapott nn/hh/ee és nn/hh/eeee formátumú dátumokat írja ki:
Új elem a \b, ami a szóhatárokra illeszkedik, azaz 01/02/03 illeszkedig, de már 001/02/03 és 01/02/030 nem.
A match hívás egy Captures példánnyal vagy ezek gyűjteményével tér vissza. Egy ezt jól bemutató példa a köetkező:
A következő példa jól bemutatja az általános algoritmust, amikor meg akarjuk számolni a találatok számát:
A count művelet az std.algorithm csomagban található. A példában újabb elemek figyelhetőek meg. A \p{xxx} azt jelentené, hogy xxx tulajdonságú karakterek illesztése történjen, \P{xxx} pedig azt jelenti, hogy az xxx tulajdonságnak nem megfelelő karakterek illeszkedjenek, azaz jelen esetben \P{WhiteSpace} azt jelenti, hogy olyan karaktereket fogad el, melyek nem fehér szóközök. Ezen tulajdonságok az unicode standardbeli tulajdonságoknak felelnek meg, melyeket ezen linken lehet megnézni: UTS 18
Most, hogy a keresést elég jól megtárgyaltuk, itt az ideje a cserék bemutatásának. Következő példában hh/nn/eeee formátumú dátumot alakítunk át eeee-hh-nn formátumúra:
Mivel a ”g” flag meg van adva, ezért minden előfordulást lecserél, nem csak az elsőt. A $1, $2, $3 kifejezések behelyettesítődnek a megfelelő csoportra illeszkedő szöveggel, azaz $1 a hónap, $2 a nap és $3 az év. (Csoportokat úgy képezünk, hogy a megfelelő részét a reguláris kifejezésnek zárójelbe tesszük.) Nem csak csoportokat lehet hivatkozni a helyettesítendő stringben, használható az illeszkedés előtti rész ($`), és az illeszkedés utáni rész ($') is
Következő példában Eurót (és centeket) alakítunk át forintra reguláris kifejezésekkel, és delegate függvénnyel. A program paraméterben egy fájlnevet vár, melynek tartalmában átváltja az eurót forintra.
A reguláris kifejezésben (?P<név>kifejezés) kifejezéssel tudunk
egy részkifejezésnek (csoportnak) nevet adni.
Ezután a kódban a csoportra captures[”név”] kifejezéssel tudunk hivatkozni.
A replace!műveletnév(text, regex) pedig az adott szövegre illeszti a reguláris kifejezést,
majd minden találatnál meghívja a megadott műveletből képzett delegate-et,
paraméterül adva a találat captures tagját. Fontos a g flag megadása,
hisz ez nélkül csak az első eurót váltaná át forintra,
ezzel viszont minden előfordulást lecserél.
A megadott művelet visszatérési értékével fogja majd lecserélni
a kifejezésre illeszkedő szöveget, majd az így kapott string-el tér vissza.
A következő fájlból:
A következő kimenetet generálja:
A splitter segítségével képesek vagyunk egy szöveget felosztani több részre. Ott történik a felosztás, ahol a reguláris kifejezés illeszkedik a szövegre, ezen elválasztók nem lesznek részei a kapott stringeknek.
A fenti példa mondatokra osztja a szöveget, a mondatok közötti fehér szóközök törlődnek, azonban az írásjelek nem. Egy új elem a (?<=kifejezés) mely azt jelenti, hogy a kifejezés illeszkedése esetén az illeszkedő pont a kifejezés után legyen. Azaz a (?<=abc)d azt jelenti, hogy az abc karaktersorozat c betűje után ha d karakter következik, akkor a d karakter illeszkedik. Az illesztett szöveg nem az abcd lesz, hanem csak a d. Ez azért kell a fenti kifejezésbe, hogy az írásjelek ne legyenek az elválasztójel karakterei között, de azok után történjen az elválasztás.
Ha gyakran használunk egy reguláris kifejezést, akkor érdemes csak egyszer elkészíteni a regex objektumot, és azt használni többször:
Azonban mivel a fenti esetben már fordítási időben ismert a reguláris kifejezés, ezért célszerű már fordítási időben legenerálni az illesztő algoritmust, hogy az ne vegyen el időt a program futtatásakor.
Fontos kiemelni, hogy bár fenn a példák az egyszerűbb megértés érdekében a match és replace művelettel vannak bemutatva, hisz a legtöbb nyelvben is ezeket használják, azonban a matchFirst, matchAll, replaceFirst, replaceAll műveleteket érdemes helyettük használni, már csak az olvashatóság kedvért is.
Minta | Jelentés |
Bármilyen karakter a következők kivételével: [{|*+?()^$ | Az adott karakter illeszkedik. |
. | Ha x flag meg van adva, akkor bármi illeszkedik, egyébként pedig a \n és \r karaktereken kívül bármi. |
[felsorolás] | A felsorolt karakterek mindegyike illeszkedik. Felsorolás helyett lehet intervallum is, pl. [a-h] |
[^felsorolás] | A felsorolt karaktereken kívül minden illeszkedik. Felsorolás helyett lehet intervallum is, pl. [^a-h] |
\cC | C karakternek megfelelő vezérlő karakter illeszkedik. |
\xXX | Az XX hexadecimális értékű karakter illeszkedik. |
\uXXXX | Az XXXX hexadecimális értékű karakter illeszkedik. |
\U00YYYYYY | Az YYYYYY hexadecimális értékű karakter illeszkedik. |
\f | A formfeed karakter illeszkedik. |
\n | A linefeed karakter illeszkedik. |
\r | A kocsivissza (cr) karakter illeszkedik. |
\t | A tab karakter illeszkedik. |
\v | A vertikális tab karakter illeszkedik. |
\d | Bármilyen számjegy illeszkedik (1,2,3,4,5,6,7,8,9,0). |
\D | Minden karakter illeszkedik kivéve a számokat. |
\w | Bármilyen karakter, mely előfordulhat egy szóban, az illeszkedik, beleértve a számokat is. |
\W | Olyan karakterek illeszkednek, melyek nem fordulhatnak elő szavakban. |
\s | Bármilyen fehér szóköz karakter illeszkedik. |
\S | Fehér szóközön kívül bármilyen karakter illeszkedik. |
\\ | \ karakter illeszkedik. |
\c ahol c a következők egyike: [|*+?() | Az adott c karakter illeszkedik. |
\p{Tulajdonságnév} | Az adott tulajdonságba tartozó karakter illeszkedik. Egy betűs rövidítés is használható, ekkor a {,} jeleket nem kell használni. |
\P{Tulajdonságnév} | Az adott tulajdonságba nem tartozó karakter illeszkedik. Egy betűs rövidítés is használható, ekkor a {,} jeleket nem kell használni. |
* | Előző mintát próbálja valahányszor (akár nullaszor) illeszteni. Mohó verzió, a lehető legtöbbet próbálja illeszteni. |
*? | Előző mintát próbálja valahányszor (akár nullaszor) illeszteni. Lusta verzió, a lehető legkevesebbet próbálja illeszteni. |
+ | Előző mintát próbálja legalább egyszer illeszteni. Mohó verzió, a lehető legtöbbet próbálja illeszteni. |
+? | Előző mintát próbálja legalább egyszer illeszteni. Lusta verzió, a lehető legkevesebbet próbálja illeszteni. |
{n} | Előző mintát próbálja pontosan n-szer illeszteni. |
{n,} | Előző mintát próbálja legalább n-szer illeszteni. Mohó verzió, a lehető legtöbbet próbálja illeszteni. |
{n,}? | Előző mintát próbálja legalább n-szer illeszteni. Lusta verzió, a lehető legkevesebbet próbálja illeszteni. |
{n,m} | Előző mintát próbálja legalább n-szer illeszteni, de legfeljebb m-szer. Mohó verzió, a lehető legtöbbet próbálja illeszteni. |
{n,m}? | Előző mintát próbálja legalább n-szer illeszteni, de legfeljebb m-szer. Lusta verzió, a lehető legkevesebbet próbálja illeszteni. |
(minta) | Az adott mintát próbálja illeszteni, a mintát egy csoportba foglalva, később hivatkozni tudjunk rá. |
(?:minta) | Az adott mintát próbálja illeszteni, a mintát egy csoportba foglalva, de nem menti le, azaz később nem tudunk rá hivatkozni, de az illesztés gyorsabb lesz. |
A|B | Illeszkedik, ha A vagy B minta illeszkedik. |
(?P<név>minta) | Az adott mintát próbálja illeszteni, a mintát egy csoportba foglalva, később hivatkozni tudjunk rá, a megadott névvel is. |
^ | A szöveg elejére illeszkedik, illetve többsoros mód esetén (m flag) a sortörésekre is. |
$ | A szöveg végére illeszkedik, illetve többsoros mód esetén (m flag) a sortörésekre is. |
\b | Szóhatárra illeszkedik (szó végére és szó elejére). |
\B | Akkor illeszkedik, ha a pozíció nem szóhatár. |
(?=minta) | Az aktuális pozíció (nulla hosszúságú illesztés) akkor illeszkedik, ha azt a mintára illeszkedő szöveg követi. (Előrenéz.) |
(?!minta) | Az aktuális pozíció (nulla hosszúságú illesztés) akkor illeszkedik, ha azt olyan szöveg követi, mely nem illeszkedik a mintára. (Előrenéz.) |
(?<=minta) | Az aktuális pozíció (nulla hosszúságú illesztés) akkor illeszkedik, ha azt a mintára illeszkedő szöveg előzi meg. (Hátranéz.) |
(?<!minta) | Az aktuális pozíció (nulla hosszúságú illesztés) akkor illeszkedik, ha azt olyan szöveg előzi meg, mely nem illeszkedik a mintára. (Hátranéz.) |
Referencia | Erre cserélődik le |
$& | Teljes illesztés. |
$` | Bemenet illesztés előtti része. |
$' | Bemenet illesztés utáni része. |
$$ | A $ karakter. |
\c , ahol c bármilyen karakter | A megadott c karakter. |
\\ | '\' karakter. |
$1 .. $99 | Adott sorszámú csoportra (zárójelbe tett regex részkifejezés) illeszkedő szöveg. |
Flag | Jelentés |
g | Globális regex, összes illesztést megkeresi, nem csak az elsőt. |
i | Ne legyen érzékeny kis- és nagybetűkre. |
m | Több soros mód, ^ és $ jelek nem csak a szöveg elejére és végére illeszkednek, hanem a sortörésekre is. |
s | Egy soros mód, a . illeszkedni fog a \n és \r karakterekre is. |
x | Fehér szóközöket ignorálja a mintában. |