Makrónak nevezzük az olyan programokat, amelyek a fordítási folyamat során futnak le, és eredményük a makróhívás helyére épül be. Ezáltal lehetőségünk van a forráskód magasabb szintű szerkezeteinek automatizált generálására. Ilyenkor a makrók használatával a program áttekinthetőbbé válik, valamint a megírt makrók más programokban is felhasználhatók. A metaprogramozás azt a programozási módszert jelenti, amikor a fordítási időben végrehajtott makrók és a futásidőben végrehajtott programok "összekeverednek", és a kétszintű hierarchia helyét bonyolult kölcsönhatások rendszere veszi át.
A Template Haskell egy Haskell98-kiegészítés, amely lehetőséget ad a típusbiztos fordítási idejű meta-programozásra. Template Haskell esetén a makrókat magukat is Haskell nyelven írjuk.
Haskell nyelvű programokban akkor lehet makrókra szükségünk, amikor függvényekel nem írható le az az általánosítás, aminek a szintjén a megoldásunkat meg akarjuk fogalmazni, például azért, mert nem lenne típusozható.
A továbbiakban egy konkrét példán keresztül mutatjuk be a Template Haskell működését. A példánk a standard Prelude-ben megtalálható fst illetve snd függvények általánosítása lesz. A két függvény típusa, illetve definíciója az alábbi:
Ezek tehát a rendezett párok vetítő-függvényei: az fst egy rendezett pár első elemét, míg az snd a második elemét adja vissza. Hasonlóképpen megírhatnánk a vetítést rendezett hármasokra is:
De mi a helyzet, ha általában rendezett n-esek k-adik tagját elérő függvényt akarunk készíteni? Nyilván nem írhatunk olyan függvényt, amelyiknek az n és a k futásidejű paramétere, hiszen például a függvény visszatérési típusa függ k-tól, ahogyan az fst illetve snd típusának összevetéséből is látszik.
Természetesen adott n-re illetve k-ra elkészíthetjük a vetítőfüggvényeket, ahogyan azt fent n=2-re illetve n=3-ra meg is tettük, vagy például n=4, k=2-re:
A Template Haskell arra ad megoldást, hogy tudjunk írni olyan programot, amely fordítási időben előállítja a fenti alakú függvényeket. Ez a függvény beépül a fordítóprogramba, és tetszőleges n-re illetve k-ra generáltathatunk vele vetítőfüggvényeket.
A Template Haskell tehát olyan, Haskell nyelvű programok írására szolgál, amelyeknek eredménye is Haskell nyelvű program. Ehhez természetesen szükség van egy interfészre, ami a makrók és a fordítóprogram között van.
Ez a gyakorlatban azt jelenti, hogy a Template Haskell részét képezik a Haskell kifejezések, minták és definíciók reprezentálására alkalmas algebrai adattípusok, és a makrók ebben a reprezentációban állítják elő a kimenetüket. Ez a reprezentáció gyakorlatilag megfelel a Haskell kifejezések szintaxisfájának.
Például az előbbiekben bemutatott fst3 (avagy proj_3_1) függvény definíciója (az egyszerűség kedvéért minták helyett lambda-kifejezést használva):
A jobboldali lambda-kifejezés TH reprezentációja:
A készítendő proj makrónk feladata tehát, hogy a fenti szintaxisfát adott n-re illetve k-ra elkészítse. Ez történhet például a következő programmal:
Kipróbálhatjuk, hogy n=3-ra és k=1-re (átnevezésektől eltekintve) visszakapjuk a fenti kifejezés szintaxisfáját:
proj 3 1 == LamE [TupP [VarP "x1", VarP "x2", VarP "x3"]] (VarE "x1")
A makróknak szüksége lehet egyedi azonosító-nevek generálására, nehogy a makróalkalmazás helyén lévő nevek összegabalyodjanak a makróból kieső nevekkel. A Template Haskellben ezért a makróknak az egyedi nevek generálását végző Q monádban kell a számításaikat elvégezni. Ebben a monádban a newName nevű kiszámítással hozhatunk létre új, valamilyen adott prefixű neveket.
A fenti makró végleges alakja tehát az alábbi:
Mi a teendő, ha ki akarjuk próbálni a makrónkat? Kézenfekvő ötlet, hogy GHCi-ből megnézzük, mit is állít elő. Ehhez azonban szükségünk van a Q monád futtatására. A runQ függvény segítségével a Q-beli kiszámításokat "felemelhetjük" az IO monádba:
A szintaxisfa előállításának egy kényelmes módját jelenti a Template Haskell azon lehetősége, hogy a [|.|] jelek közé zárt kifejezések értéke a benne lévő kifejezés szintaxisfája, a változónevek konzekvens egyediesítésével. Például az alábbi két program ekvivalens:
A fentieknek megfelelően definiált makrókat a $() szintaxis segítségével használhatjuk fel a programunkban. A $() zárójelei közötti kifejezést fordítási időben, egy alkalmas Q monádban számítja ki a fordítóprogram, és az eredményével helyettesíti a forrásban.
Fontos megkötés, hogy egy modulban nem használhatunk ugyanabban a modulban definiált makrót. Kivételt képez ez alól a [|.|] jelekkel "idézőjelbe tett" kód belseje: itt tetszőleges belső hivatkozás is állhat.
Például az alábbi program a fenti módon definiált proj makrót használja: