Friday, February 6, 2009

DSL - jazyky ušité na míru. Část druhá

V první části tohoto článku jsem se pokusil nastínit základní koncepci DSL a na příkladu objasnit základní principy vývoje. V této části se budu věnovat problematice návrhu jazyka a generátoru kódu. Vycházím z přednášky Domain-Specific Modeling: Enabling full code generation1), kterou přednesl Juha-Pekka Tolvanen na konferenci OOP2009 v Mnichově.

DSL se snaží vyhnout promítání mentálních obrazů z hlavy zadavatele do prostoru obecného jazyka. Chce takto minimalizovat zkreslení požadavků a zanesení chyb do aplikace. Zadavatel má určitou představu o fungování aplikace, kterou je třeba transformovat do hotového produktu. Tato transformace bývá často bolestná a je tím bolestnější, čím větší propast musíme překlenout mezi abstrakcemi ve světě uživatele a prvky použitého programovacího jazyka. Popisuje-li zadavatel složité vztahy mezi tlačítky, stavy hodinek a displejem, pohybuje se s jistotou ve svém mentálním prostoru, který si vybudoval za léta praxe v oboru. Vypráví příběh, kterému se tvůrce aplikace snaží porozumět a přeformulovat si ho do svého mentálního prostoru, který je vymezen použitými technologiemi a zkušenostmi. Rozhodne-li se tvůrce promítnout tento příběh přímo do kódu Assembleru, bude muset vynaložit značné úsilí kvůli propastnému rozdílu mezi úrovněmi abstrakcí v obou světech. Může také použít nějaký vyšší programovací jazyk, dejme tomu C++, avšak rozdíl v míře abstrakce se významně nezmenší. Programátor bude stále operovat s pojmy značně vzdálenými mentálním obrazům z hlavy zadavatele. Další možností je modelovat systém vizuálně v UML, poté generovat kód do C++ nebo Javy a odtud do Assembleru. Světy zadavatele a programátora se sblíží, nicméně stále bude zapotřebí promítat mentální obrazy zadavatele do prvků jazyka UML.

Na nejmenované švédské univerzitě se kdysi prováděl experiment, který měl ověřit přenositelnost modelů navržených v UML mezi různými nástroji. Testovalo se pět nástrojů, které nabízely export a import modelů ve standardním formátu XMI. V jednom z nástrojů se navrhl jednoduchý model obsahující jedinou třídu a vyexportoval se do XMI. Poté se experimentátoři snažili nahrát model do ostatních nástrojů. Ani v jednom případě se to nepodařilo.

Důsledkem tohoto procesu je, že se o objektech z nějaké oblasti a vztazích mezi nimi vyjadřujeme v cizím jazyce. Hlavní přínos DSL je v tom, že umožňuje popisovat objekty a vztahy v rodném jazyce.

Návrhu doménově-specifického jazyka obecně předchází řada projektů, během nichž postupně roste potřeba speciálního jazyka. V okamžiku, kdy požadavky na něj vykrystalizovaly do dostatečně konkrétní podoby, je možno začít vytvářet jazyk. Předpokládá se, že ve firmě lze najít pár skutečných expertů v oboru, kteří budou jazyk vyvíjet.

Výsledkem vývoje jsou tři produkty: meta-model (gramatika) jazyka, generátor kódu a framework, který interpretuje generovaný kód. V následujícím odstavci se budu věnovat návrhu meta-modelu jazyka.




Vývoj meta-modelu jazyka

Vývoj meta-modelu standardně probíhá iterativně, přičemž v každé iteraci se postupuje ve čtyřech krocích:

1. Identifikace abstrakcí

Cílem tohoto kroku je vytvořit pojmosloví a koncepty jazyka. Vrátíme-li se k digitálním hodinkám, výsledkem by mohly být tyto koncepty: tlačítka, displej, stavy hodinek, stopky, světový čas, proměnná, systémový čas, nastavení hodinek atd.

2. Specifikace meta-modelu

V tomto kroku se pracuje s koncepty, které vzešly z předchozího kroku. Analyzují se vztahy mezi nimi a stanovují se pravidla pro interakce a závislosti mezi nimi. Např. se stanoví, že spojnice stavového přechodu se může nakreslit pouze mezi dvěma stavy, akce musí být napojena nějakou spojnici stavového přechodu, akce musí mít jednoznačný identifikátor atd. Výsledkem této analýzy je gramatika popsaná v nějakém jazyce pro meta-modelování, jako je např. Eclipse Modeling Framework (EMF).

3. Návrh notace

Pro snadnější orientaci v modelu je vhodné opatřit koncepty jazyka odpovídají notací, např. ikonou nebou stylem čáry. Každý jistě ocení, když tlačítko v modelu připomíná tlačítko a není reprezentováno prostým obdélníkem. Na přednášce Tolvanen předváděl modelovací nástroj MetaEdit+, ve kterém je možné nakreslit notaci i během navrhování modelu v DSL. Kresby se automaticky ukládají do meta-modelu jazyka, takže v příště při navrhování jiného modelu se prvky správně zobrazují.

4. Definice generátoru

Jako poslední přichází na řadu definice generátoru kódu z modelu. Je třeba uvést, že je běžné definovat více generátorů a výstupů. Postupů, jak překládat model do kódu existuje řada a budu se jimi zabývat níže. V jednom z postupů se prolézá model pomocí programátorského rozhraní a pro každý nalezený koncept se generuje předem připravený fragment kódu. Ten může být parametrizovaný, přičemž hodnoty parametrů se získávají z aktuálně zpracovávaného konceptu.

Během vývoje jazyka se vývojáři snaží znovu používat komponenty a knihovny z předchozího vývoje. Důraz se přitom klade na iterativnost procesu a předávání prototypu jazyka k vyzkoušení ostatním vývojářům, kteří v něm budou vyvíjet, co nejdříve. Takto se získá potřebná zpětná vazba která poslouží k rozšíření a úpravě meta-modelu a generátoru.

Přístupy k identifikaci konceptů

Pro začátečníky je identifikace konceptů jazyka často tvrdým oříškem. Pro nalezení vhodné sady konceptů se doporučuje analyzovat zhruba kolem 20 příkladů použití jazyka. Až dosud se podařilo zformulovat pět rozdílných přístupů k identifikaci konceptů v prvních fázích vývoje jazyka.

1. Koncepty doménového experta

Jedná se pravděpodobně o nejjednodušší přístup, který nejlépe funguje v zavedených doménách. Koncepty poskytne expert z oblasti a generování kódu je většinou přímočaré. Jazyky, které vzniknou tímto způsobem, jsou snadno srozumitelné i ne-programátorům, kteří často mohou sami model upravovat nebo vytvářet.

2. Generování výstupu

V tomto přístupu se koncepty identifikují z výstupních artefaktů. Příklad takového
artefaktu může být konfigurační skript či konfigurace hlasové aplikace. Zde obecně platí,
že není problém vystihnout statické části, tj. datové struktury. Složitější může být
postihnout chování systému. Zde návrhář čelí pokušení navrhovat plnohodnotný programovací
jazyk. S touto tendencí souvisí sklon k nedostatečnému abstrahování konceptů.

3. Fyzická struktura

Jak název napovídá, tento přístup funguje nejlépe pro fyzické systémy, např. sítě,
logistické systémy, hardwarová architektura, řízení vlakového provozu či řízení výroby v
továrnách. Většinou se navrhuje statický systém s propojeními a závislostmi. Může však
obsahovat i dynamické prvky. Identifikace konceptů probíhá většinou hladce a dosahuje se vysoké úrovně abstrakce.

4. Look and feel systému

Koncepty lze také identifikovat analýzou uživatelského prostředí. Tento přístup jsem použil v příkladu s hodinkami, kde se operovalo s koncepty jako tlačítko, displej a stav hodinek. Identifikace konceptů je většinou bezproblémová a vede k vysoké míře abstrakce. Generátory nepředstavují větší problém.

5. Prostor proměnlivosti systému

V tomto přístupu se návrhář jazyka pomocí konceptů snaží vystihnout proměnlivost systému. Modelování produktu pak spočívá v rozhodování se mezi nabízenými variantami prvků. Má se jednat o nejnáročnější proces identifikace konceptů.

V praxi se obvykle volí kombinace dvou přístupů, přičemž nejlepších výsledků bylo dosaženo kombinací identifikace konceptů z uživatelského rozhraní (4) a analýzou prostoru proměnlivosti systému (5).

Vývoj generátoru

Účelem generátoru je překlad modelu do odpovídajícího výstupu. Překlad probíhá tak, že se prolézá struktura modelu a z jednotlivých prvků modelu se získávají potřebné informace. Ty se použijí pro překlad do výstupního kódu.

Generátor je vhodné navrhnout tak, aby primárně řešil náš úkol, jelikož pokusy o vynalezení obecného generátoru obvykle končí neúspěchem. Generátor by měl generovat 100% kód, který není určen k ručním úpravám. Pokud se narazí na problém v generovaném kódu, má se upravit generátor a nikoliv kód. Generovaný kód by měl být co nejstručnější a snadno čitelný, měl by obsahovat pouze nejnutnější instrukce, většina funkcionality by měla být podchycena frameworkem (viz níže). Generátor samotný by měl být co nejjednodušší a modulární, aby jej bylo možno snadno pozměnit při výskytu problému.

Typy generátorů

1. Použití API

Zde se předpokládá, že model lze analyzovat pomocí API poskytované modelovacím nástrojem. Nevýhoda tohoto přístupu tkví v nízkoúrovňovém zpracování modelu a těsné vazbě mezi generátorem a modelovacím nástrojem.

2. Návštěvník modelu (model visitor)

V tomto přístupu se každé struktuře modelu přiřadí fragment struktury výstupního kódu. Nevýhodou je omezení na jednoznačné přiřazení.

3. Výstupní šablona

Další možností je navrhnout šablonu výstupního kódu s instrukcemi generátoru. Celá šablona je většinou uložena v jediném souboru. Výhodou je jednoduchost.

4. Lezoun (Crawler)

Tento generátor je navržen k procházení modelu a je schopen samostatně se navigovat v modelu. Generovaný kód posílá do výstupního proudu.

5. Generátor generátorů

Zde se předpokládá, že máme k dispozici generátor, který nám vygeneruje specializovaný generátor k našemu meta-modelu. Lákavá myšlenka, avšak většinou se ukáže, že takového super-generátoru není zapotřebí.

Framework

Framework je poslední ze tří základních stavebních kamenů doménově-specifického modelování. Představuje rozhraní k cílové platformě skrývající nepodstatné detaily. Usnadňuje návrh generátoru, neboť ten se může soustředit pouze na generování nezbytného kódu, který bezprostředně souvisí se zpracovávaným modelem. Framework současně zvyšuje míru abstrakce, takže generátor může pracovat v pojmosloví, které je mu bližší. Současně omezuje vznik duplicit v generovaném kódu a napomáhá znovupoužití komponent vyvinutých dříve. Dále usnadňuje přenositelnost, neboť vyvstane-li potřeba adaptovat produkt pro jinou cílovou platformu, bude nutné přepsat pouze framework.

Nástroje pro DSM

Tolvanen nabízí šest způsobů, jak si pořídit nástroj pro doménově specifické modelování:

1. Napsat si vlastní software od začátku
2. Napsat si vlastní software nad nějakým existujícím frameworkem
3. Navrhnout meta-model jazyka v nějakém existujícím nástroji a vygenerovat si kostru vlastního nástroje
4. Navrhnout meta-model jazyka a vygenerovat si plně funkční nástroj pracující nad nějakým frameworkem
5. Navrhnout meta-model a ten použít jako konfiguraci pro nějaký existující generický modelovací nástroj
6. Pořídit si integrované modelovací a meta-modelovací prostředí (např. MetaEdit+)

Na internetu jsem posbíral reference na několik nástrojů pro DSM:

Shrnutí

Ze zkušeností vyplývá, že doménově-specifické modelování významně zvyšuje produktivitu (odhady jsou 5-10-krát.) Klade se důraz na znovupoužitelnost komponent a oddělování zájmů. V podstatě se jedná o radikální refaktoring.

Reference:
1) Domain-Specific Modeling: Enabling full code generation, Juha-Pekka Tolvanen, OOP2009, Munich

2) www.DSMforum.org
3) www.DSMbook.com

Tuesday, February 3, 2009

DSL - jazyky ušité na míru. Část první

Minulý týden (26-31.1.2009) jsem trávil v Mnichově na pravidelné programátorské konferenci OOP. Událost to byla vskutku grandiózní, každý den probíhalo souběžně několik přednášek na žhavá současná témata z oblasti objektově orientovaného programování a z přilehlých oblastí, prokládaných keynotes, čili odlehčenými prezentacemi celebrit z oboru. První den jsem podstoupil celodenní školení v oblasti, o které jsem doposud věděl velmi málo, nicméně která mě podvědomě lákala - Domain-Specific Languages. Konkrétně se jednalo o přednášku Domain-Specific Modeling: Enabling full code generation, kterou přednesl Fin Juha-Pekka Tolvanen1). V následujícím textu se pokusím srozumitelně shrnout, co jsem si z této přednášky odnesl.

V současnosti lze vysledovat dva protichůdné trendy v návrhu programovacích jazyků. V prvním z nich je patrná snaha o zlepšení vyjadřovacích schopností jazyka a elegance zápisu. Jako příklad vezměme jazyk Scala, který díky svým jazykovým prvkům jako jsou traits, mixins, function values a silná typová kontrola, otevírá architektům, designerům a vývojářům nové obzory. O tom však v nějakém příštím blogu.

V druhém proudu se naopak klade důraz na schopnost jazyka vyjádřit vazby a interakce v systémech pouze z jedné konkrétní oblasti (domény) a redukovat počet stupňů volnosti jazyka. V takovém jazyce se operuje s pojmy, kterým rozumí specialisté z dané oblasti, z čehož plyne řada blahodárných důsledků, např. usnadnění komunikace během vývoje produktu.

Uvažujme fiktivního výrobce digitálních hodinek, kterému po té, co uvedl na trh několik modelů hodinek, začíná být jasné, že při vývoji programového vybavení se opakovaně používají stejné moduly, jako např. čas, budík, stopky, tlačítka, display atd. Navíc se zdá, že používaný programovací jazyk (dejme tomu C) je pro potřeby aplikace v hodinkách zbytečně silný a jeho bohaté vyjadřovací schopnosti často vedou k chybám v kódu.

Nabízí se otázka, zda by bylo možné aplikaci pro hodinky modelovat ve specializovaném modelovacím jazyce, pokud možno vizuálně, a neprogramovat ji ve zbytečně silném jazyce. Model by obsahoval prvky jako jsou tlačítka, displej, stavy, proměnné atp., a současně by definoval jejich vlastnosti a vzájemné interakce. Z modelu by se pak vygeneroval kód do obecnějšího jazyka. Na následujícím obrázku je ukázka takového modelu
2) . V tomto případě se jedná o model stopek v hodinkách.

Modré obdélníky odpovídají stavům hodinek, šedé válečky tlačítkům, žlutá A jsou symboly pro akce a zelené rámečky představují displej. Bílé rámečky reprezentují paměťová místa, tedy proměnné. Jednotlivé prvky jsou propojeny spojnicemi, jejichž význam je dán propojovanými prvky a stylem čáry. Spojnice mezi stavy odpovídá stavovému přechodu, zatímco spojnice mezi tlačítkem a přechodem představuje spouštěcí událost. Spojnice mezi žlutým A a proměnnými definují operaci prováděnou akcí. Na spodní hraně obrázku lze z modelu vyčíst, že akce, která odpovídá zastavení stopek, čte systémový čas z proměnné sysTime, odečítá od něj hodnotu přečtenou z proměnné startTime a výsledek ukládá do proměnné stopTime.

Pozn.: Černé kolečko symbolizuje start činnosti modulu (tzv. počáteční pseudostav), černé kolečko s bílým prstencem symbolizuje konec činnosti (tzv. koncový pseudostav).

Uvedený model můžeme interpretovat takto: stopky se po aktivaci nacházejí ve stavu Stopped. Stiskem tlačítka Down se vynuluje počítadlo stopTime, jelikož se mu přiřadí rozdíl hodnot systémového času sysTime (stopTime = sysTime - sysTime). Stiskem tlačítka Up se přejde do stavu Running, přičemž se do proměnné startTime nastaví rozdíl sysTime - stopTime. Opětovným stiskem tlačítka Up se přejde zpět do stavu Stopped, přičemž do proměnné stopTime se uloží rozdíl
sysTime - startTime. Jak lze snadno nahlédnout, uvedené aritmetické operace vedou k akumulaci naměřených intervalů v proměnné stopTime. Stisk tlačítka Mode vede k ukončení činnosti stopek. Displej ve stavu Stopped ukazuje hodnotu stopTime v sekundách, čili naměřený čas. Ve stavu Running pak displej zobrazuje rozdíl mezi systémovým (aktuálním) časem a časem spuštění stopek (startTime).

Pro každý modul v hodinkách (např. světový čas) lze namalovat podobný model. Budou v něm figurovat prvky jako tlačítka, akce, přechody, stavy, proměnné, display a další. Propojování prvků mezi sebou a jejich vlastnosti se řídí pravidly. Není možné například propojit stav a tlačítko spojnicí stavového přechodu. Touto spojnicí lze propojit pouze dva stavy. Souhrn těchto pravidel tvoří gramatiku doménového jazyka a zveme ji metamodel. Metamodel je svou povahou také DSL sloužící k modelování jiných DSL.

Nabízí se přirozeně otázka, na kolik vlastně DSL zvyšují produktivitu? Podle výzkumu z roku 2002 3) vede zavedení DSL k rapidnímu zvýšení produktivity. Porovnání nabízí následující tabulka, ze které lze nahlédnout, jak zavedení jednotlivých programovacích jazyků zvýšilo produktivitu.






























JazykPočet nových vlastností produktu implementovaných v daném čase (Normalizováno k Assembleru)
Assembler1
Fortran4,5
Basic5
C++ (UML)6
Java (UML)6
Domain-Specific40

Z uvedené tabulky vyplývá, že zavedení klasických programovacích jazyků zvýšilo produktivitu pouze nepatrně. Zakopaný pes je v nízké úrovni abstrakce prvků (concepts) uvedených jazyků. Jinak řečeno, tyto prvky jsou svou povahou velmi vzdáleny od objektů z modelované oblasti. Vráťme se k příkladu s hodinkami. Koncept tlačítka je v našem DSL reprezentován jediným symbolem, přičemž v Javě by mu odpovídal kód o mnoha řádkách, o Assembleru nemluvě. Schéma celého aparátu stopek je složeno z řádově desítky objektů.

Kde aplikovat?

Lze snadno a oprávněně namítnout, že stručnost zápisu modelu je přímým důsledkem designu DSL, který byl ušit na míru úzké oblasti výroby digitálních hodinek. Vývoj takového jazyka jistě něco stojí a není vůbec jisté, že se vyplatí. Ano, DSL nelze použít všude a za všech okolností. K tomu, aby jeho vývoj a použití vedly k významnému zvýšení produktivity, je zapotřebí důkladné analýzy a především - schopných specialistů z dané oblasti, kteří mají, pokud možno, zkušenosti s návrhem DSL. Trošku začarovaný kruh. Nicméně zhruba řečeno, uvažovat o adopci DSL má smysl tehdy,
provádí-li se opakovaně stejné vývojové činnosti, které vyžadují úzkou specializaci.


V příštím blogu se budu podrobněji věnovat modelování v DSL (DSM).

Reference:
1) Domain-Specific Modeling: Enabling full code generation, Juha-Pekka Tolvanen, OOP2009, Munich
2) Software Productivity Research & Capers Jones, 2002
3) Model hodinek: http://www.methodsandtools.com/archive/archive.php?id=50

About Me

My photo
Cokoliv říkáme, je až na výjimky jinak.

Followers