Bizonyos lehetőségeket elveszítünk azáltal, hogy a Java nyelv nem támogatja az operátorok használatát. Így például egy matematikai DSL-ben a mátrixok közötti szorzást csak m1.product(m2) alakban tudjuk megadni, nem a természetesebb m1 * m2 vagy m1 x m2 alakban.
Továbbá a Java nyelv meglehetősen bőbeszédű. Emiatt nem igazán lehet tömör DSL kódot írni, ha a DSL nyelvünk Javaba van beágyazva.
Jó példa a Java-ban készített DSL-re a JMock könyvtár, amelynek segítségével olyan objektumok alkothatóak meg, amikkel más objektumok viselkedését lehet tesztelni. Az objektum készítésekor rögzíteni lehet, hogy milyen műveletek fognak meghívódni az objektumon, és milyen paramétereket fog kapni.
Egy példa a JMock használatára:
Látható, hogy az osztállyal szembeni elvárásokat könnyedén és természetes módon írhatjuk le. Egy kissé összetettebb példa az elvárásokra:
Ahhoz, hogy ilyen egyszerűen lehessen ezt az eszközt használni, a jMock tervezői több hasznos felülettervezési technikát is kihasználtak.
Kiolvasva: A mock objektum elvárja (pontosan) egyszer az „m” függvény meghívását paraméterek nélkül az „n” függvény meghívása után, és erre 20-at ad vissza.
A folytonos felület továbbfejlesztése. Lehetővé teszi, hogy korlátozzuk az objektumon használt beállító függvényeket. Úgy valósítható meg, hogy a beállító függvények immár nem az objektumra adnak vissza referenciát, amelyen meghívták őket, hanem előállítják a következő beállítási fázis objektumát.
Például így:
Ennek köszönhetően az olyan, hibás használata a beállításoknak, mint az alábbi példa:
Nem lesz a Java szemantikája szerint érvényes program, mert a withNoArguments meghívása után az eredmény egy OrderSyntax objektum lesz, amin nincs will függvény.
Ezzel a módszerrel lehetővé válnak az opcionális beállítási lehetőségek is. Használható arra is, hogy objektumokat lépésenként hozzunk létre, de arra is, hogy valamilyen cselekvéssorozatot elvégezzünk velük.
Szintaktikus cukor konténer segítségével
A Java nyelvben nincsenek globális függvények. Emiatt alapvetően nem írhatók olyan függvények, mint az alábbi például az alább kiemelt függvények (a BuyerTest osztálynak nem függvényei, és nem is örökli őket).
Ezeket a statikus függvényeket az Expectations osztálytól kapjuk. Itt nem egészen triviális, hogy miért szerepelnek dupla kapcsos zárójelek, de ha elég sokáig böngésszük a Java specifikációt, akkor rájövünk, hogy itt valójában az történik, hogy egy névtelen osztályt származtatunk az Expectation ősosztályból, majd definiálunk neki egy konstruktort.
Természetesen ilyen módszerek használata általában nem célravezető, mert nehéz megérteni, hogy egy adott kódrészlet valójában mit csinál. Azonban a DSL megalkotásának egyik lényege, hogy magasabb absztrakciós szintet ad. Valójában nem muszáj megérteni, hogy miért szükséges a dupla kapcsos zárójel, ha csak használni szeretnénk a DSL-t, akkor elfogadhatjuk, hogy a szintaxis része.
A Haskell jól használható környezetet ad DSL-ek fejlesztéséhez, akkor is, ha azok működése nem a funkcionális programozás elvei szerint történik. Sok pozitív tulajdonsága van, de leginkább a letisztultsága, a rugalmassága (nyelvbeli absztrakciók, nyelvi kiterjesztések), a szintaktikus lehetőségei (operátorok, mintaillesztés) miatt érdemes használni. Persze a funkcionális gondolkodásmóddal nem árt megbarátkozni, mielőtt az ember egy nagyobb projektbe fog.
Jó eszközöket ad a sekély DSL-ek kifejlesztéséhez, vagyis olyan DSL-ek létrehozására, amiknek nincsen a gazdanyelvben reprezentációjuk, hanem rögtön végrehajtódnak. Ez annak köszönhető, hogy kifinomult eszközök vannak a függvények használatára és manipulációjára. Ezek sok esetben hasznosak lehetnek, hasonlóan működnek egy nyelvi modul felületéhez.
Ha mégis szeretnénk reprezentációt létrehozni a DSL-ünk számára, mert például szeretnénk valamilyen transzformációt elvégezni a DSL kódon, akkor is segítségünkre lesz a Haskell. Az algebrai adattípusok segítségével szintaktikus zaj nélkül, szinte EBFN szintaxissal alkothatjuk meg a reprezentáció adattípusait.
Példaként álljon itt egy egyszerű, kifejezések leírására alkalmas DSL reprezentációja:
Ehhez könnyedén tudok készíteni egy kiértékelőfüggvényt is, ami egy kifejezésből előállítja annak eredményét.
Egy ilyen egyszerű példa persze gond nélkül továbbfejleszthető abba az irányba, hogy kezelni tudjon változókat, vagy éppen utasítások szerepeljenek benne egyszerű kifejezések helyett.
Az algebrai adattípusok jó lehetőségeket adnak a DSL típusrendszerének megalapozására. Az előző példa ArithExpr adattípusa mellé vegyünk fel egy BoolExpr-t is. Ebben az esetben lényegében kikényszerítettük azt, hogy az And két oldalán csak logikai kifejezések állhatnak, a Greater pedig két aritmetikai kifejezésből egy logikai kifejezést ad eredményül.
A típusfüggvények használatát is lehetővé tevő típusrendszer pedig sok invariánst már reprezentációs szinten is ki tud fejezni a DSL nyelv szemantikájával kapcsolatban.
A következő változatban egy megkötést vezetek be: A kifejezés típusa tetszőleges (szám típus) lehet, de ekkor minden részkifejezése is azonos típusú. Ennek az lesz az eredménye, hogy a kifejezésben levő összes literál azonos típusú.
Itt lényegében a Haskell típusrendszerét használtuk arra, hogy a DSL típusrendszerét kialakítsuk. Természetesen nem lehet minden szabályt kifejezni ilyen módon kikényszeríteni, de kezdetnek ez sem rossz.
A típusosztályok segítségével az is lehetővé válik, hogy a DSL nyelv nagyon közel álljon a hagyományos Haskell kódhoz.
Határozzuk meg például, hogy az ArithExpr adattípus a Num osztályhoz tartozik. Ekkor meg kell adnunk néhány előre meghatározott függvényt. Minden műveletnek megfeleltetjük a saját DSL-ünkbeli megfelelőjét.
Ennek két pozitív következménye lesz a nyelvünkre nézve. Elsősorban lehetővé válik, hogy kifejezéseinket ugyanúgy írjuk fel, mint a Haskell kifejezéseket.
Ez már önmagában is jelentős eredmény, hiszen DSL kifejezéseket írni semmiben sem más, mint egyszerű Haskell kifejezéseket írni. Azonban van ennek még egy előnye, a Haskell gazdag függvénykészlete immár a DSL-ünk számára is elérhető lesz. Használjuk például a sum függvényt, amely egy beépített Haskell függvény:
A Scala igazán gazdag eszközkészletet ad DSL-ek tervezésére. Az előző félév során ezzel kapcsolatban bővítettem a nyelv leírását, és van néhány érzékletes példa is. Röviden összefoglalva, a Java és Haskell nyelvnél felsorolt jó tulajdonságok közül sok megvan benne, köszönhetően annak, hogy a nyelv tervezésének során külön foglalkoztak a DSL-ek beágyazásának kérdésével. Sajnos az nem mondható, hogy a nyelv szabályai egyszerűek lennének, különösen, ha már mélyebben foglalkozunk velük.