Abstract Wikipedia/Template Language for Wikifunctions/Scribunto-based implementation
Toto je přehled dokumentace NLG systému založeném na Scribunto, založená na návrhu na vytvoření šablonovacího jazyka NLG pro použití v projektu Abstraktní Wikipedie. Systém založený na Scribuntu má dva cíle: za prvé, fungovat jako prototypová implementace, která může být podkladem pro budoucí implementaci podobného systému v samotných Wikifunkcích, a za druhé, fungovat jako samostatný NLG systém založený na lexémech Wikidat v prostředí Scribunto.
Tato stránka se skládá ze dvou samostatných částí:
- Popis průběhu kódu;
- Pokyny pro přispěvatele, kteří chtějí napsat nový abstraktní obsah nebo renderery pro konkrétní jazyky.
Průběh kódu: od Q-id k verbalizovanému obsahu
Různé moduly používané v systému mají své vlastní stránky s dokumentací. Cílem této části je poskytnout přehled o tom, jak jednotlivé komponenty spolupracují.
Průběh programu přesně kopíruje návrh architektury NLG pro Abstraktní Wikipedii.
Od Q-id k šablonám
Vstupním bodem verbalizace obsahu je funkce content
, která přebírá (v objektu frame) Q-id a (nepovinný) kód jazyka realizace (pokud kód jazyka není uveden, použije se výchozí jazyk Wiki pro uživatele).
Získání obsahu
Funkce content
nejprve načte abstraktní obsah (tj. konstruktory) pro dané Q-id pomocí modulu Constructors. Tento obsah může být jednoho ze dvou typů:
- Ručně zpracovaný abstraktní obsah pro dané Q-id, uložený v modulu Abstraktní obsah.
- Abstraktní obsah generovaný dynamicky (kódem v modulu Konstruktorů) na základě vlastností položky Wikidat, na kterou odkazuje Q-id.
Tyto dva způsoby vyhledávání obsahu byly popsány v článku zpráva o modelových článcích.
Získaný obsah má podobu seznamu instancí konstruktorů, které by měly odpovídat osnově generovaného článku.
Výběr šablony
Pro každý konstruktor je třeba v seznamu obsahu vybrat a realizovat příslušnou šablonu. To se provádí v rámci funkce content
realizací "obalové šablony" ve tvaru {root:main}
a zároveň předat funkci realizace šablony jediný argument šablony s názvem main
, jehož obsahem je samotný konstruktor. Logika realizace šablony (podrobně popsaná níže) ji bude interpretovat jako šablonu s jediným slotem, který žádá o interpolaci (tj. verbalizaci) parametru main
. Protože interpolace argumentu, který je konstruktorem, vyžaduje získání příslušné šablony, znamená to rozšíření obalové šablony o šablonu specifickou pro konstruktor (pro daný realizační jazyk).
Realizace šablony
Hlavní průběh realizace šablony probíhá v rámci modulu funkcí realizeTemplate
. Tuto funkci lze také vyvolat přímo ze stránky Wiki pomocí příkazu funkce render
(zdokumentováno zde). To je užitečné pro účely ladění nebo pokud je třeba realizovat ad hoc šablonu.
Realizace šablony se skládá z několika kroků, které se přesně řídí obecným realizačním algoritmem specifikovaným pro šablonovací jazyk (příslušné fáze tohoto algoritmu jsou uvedeny níže v závorkách).
Inicializace
Funkce initialize
nastaví některé globální proměnné, které se budou používat v celé realizační linii. Jedná se o tyto proměnné:
- Proměnná
language
obsahuje kód jazyka realizace. Buď jej zadá uživatel, nebo je nastaven na jazyk obsahu projektu. - Proměnná
functions
je tabulka všech funkcí přístupných pomocí šablon. Ty odkazují na implementaci specifickou pro daný jazyk, nebo v případě její absence na jazykově agnostickou implementaci. - Proměnná
relations
je tabulka všech relačních funkcí přístupných pomocí šablon. Ty odkazují na implementaci specifickou pro daný jazyk, nebo, pokud chybí, na implementaci jazykově agnostickou. - Proměnná
renderers
je tabulka pro renderování konstruktorů v jednotlivých jazycích. - Proměnná
applyPhonotactics
ukazuje na jazykově specifickou implementaci fonotaktického modulu.
Všimněte si, že ve Wikifunkcích by všechny tyto prvky (s výjimkou kódu jazyka realizace) měly být globálně přístupné jako funkce Wikifunkcí.
Rozbor a vyhodnocení šablony (fáze 1 a 2)
Funkce evaluateTemplate
(součást modulu TemplateEvaluator) je zodpovědná za provedení prvního kvadratického pole ve výše uvedeném blokovém schématu ("Templatic Renderer"). Jeho výstupem je kořenový seznam lexémů: uspořádaný seznam prvků, které jsou samy o sobě buď lexémy, nebo kořenovými seznamy lexémů, s dodatečným příznakem pole root
idetifikujícího jeden z těchto prvků jako kořenový. Ve výše uvedeném vývojovém diagramu je tato datová struktura pojmenována jako "strom lemmat". Všimněte si, že se fakticky jedná o mělký strom, kde všechny uzly jsou potomky kořenového uzlu (tento strom však nemusí být nutně stejný jako ten, který je určen závislostními vztahy šablony, kromě toho, že mají stejný kořen).
Tato fáze se skládá ze tří částí:
- Funkce modulu TemplateParser
parse
je volána k transformaci šablony zadané jako řetězec na dva seznamy: strukturovaný seznam reprezentující její prvky a seznam reprezentující vztahy závislosti, které se mají použít. Parser také vrací index kořenového prvku, který se uloží do reprezentace seznamu zakořeněných lexémů. Přesný formát vrácených dat naleznete v dokumentaci modulu. - Funkce
evaluateElements
prochází seznam prvků a vyhodnocuje každý prvek šablony do lexému nebo kořenového seznamu lexémů. Vyhodnocení se provádí podle typu každého prvku:- Textové prvky (ať už se jedná o normální text, mezery nebo interpunkční znaménka) se vyhodnocují pomocí funkce
TemplateText
. - Čísla (v rámci slotů) se vyhodnocují pomocí funkce
Cardinal
. - Identifikátory lexémů (v rámci slotů), tzv. L-idy, se vyhodnocují pomocí funkce
Lexeme
. - Identifikátory položek (v rámci slotů), tzv. Q-idy, se vyhodnocují pomocí funkce
Label
. - Interpolace argumentů se vyhodnocuje (v příkazu funkce
evaluateInterpolation
) načtením parametru a jeho vyhodnocením podle stejné logiky jako výše (např. pokud je argument L-id, vyhodnotí se pomocí funkceLexeme
). Speciální logika je použita v následujících případech:- Pokud se argument vyhodnotí jako dílčí šablona (tj. řetězec obsahující
{ }
), je tato šablona rekurzivně vyhodnocena pomocí funkceevaluateTemplate
(vrací seznam lexémů s kořeny). - Pokud se argument vyhodnotí jako seznam šablon variant (seznam tabulek obsahujících pole
template
), je vybrána jedna vhodná šablona podle jejích předpokladů (pomocí funkceselectTemplate
) a vyhodnocena. - Pokud se argument vyhodnotí jako Konstructor (tj. tabulka obsahující pole
_predicate
), je příslušná šablona načtena z příslušného modulu Rendererů specifického pro daný jazyk (v souladu s danými předpoklady) a vyhodnocena (to se děje v modulu funkceevaluateConstructor
).
- Pokud se argument vyhodnotí jako dílčí šablona (tj. řetězec obsahující
- Funkce jsou vyhodnocovány (ve funkci
evaluateFunction
) vyvoláním příslušné funkce se zadanými parametrx. Funkce a interpolace předané funkci jako parametry se vyhodnocují rekurzivně (vevaluateFunctionArgument
), ale ne, že ostatní typy argumentů jsou jednoduše předány jako řetězcové argumenty volané funkci bez dalšího zpracování.
- Textové prvky (ať už se jedná o normální text, mezery nebo interpunkční znaménka) se vyhodnocují pomocí funkce
- Vztahy uvedené v šabloně se použijí (v
applyRelations
): v podstatě se na příslušné prvky (nebo jejich kořeny, pokud byly prvky vyhodnoceny do seznamu lexémů s kořeny) aplikuje jakákoli relace, která odpovídá známé relační funkci definované v modulu Relations (nebo její implementaci specifické pro daný jazyk). Tyto relační funkce sjednocují gramatické rysy svých dvou argumentů podle zadané gramatické funkce, čímž efektivně propagují a sdílejí gramatické rysy mezi lexémy v souladu s celkovou jazykovou strukturou.
Aplikace morfosyntaktických omezení (fáze 3, s použitím mírného pročištění)
Výstupem poslední fáze je strom lexémů, kde je každý lexém spojen se sadou gramatických rysů, které mohou pocházet z jiného lexému. Tyto rysy mohou sloužit jako omezení, která čistí seznam tvarů každého lexému tak, aby byly zachovány pouze tvary, které jsou omezené. V této fázi již není stromová struktura potřebná, takže je zploštěna na jednoduchý seznam lexémů.
Ořezávání lexémů se provádí ve funkci applyConstraints
voláním metody filterForms modulu lexemes. Algoritmus filtrování funguje následovně:
- Iteruje přes všechny tvary lexému. Pro každý tvar:
- Iteruje gramatická omezení spojená s lexémem. Pro každou kategorii gramatických omezení:
- Pokud má tvar funkci stejné kategorie:
- Zahodí tvar, pokud vlastnost tvaru není sjednotitelná s vlastností omezení (a přejde k dalšímu tvaru).
- Pokud má tvar funkci stejné kategorie:
- Iteruje gramatická omezení spojená s lexémem. Pro každou kategorii gramatických omezení:
V prototypové implementaci je každý prvek sjednotitelný pouze sám se sebou nebo s prázdným prvkem. V plnohodnotné implementaci může být žádoucí lingvistická hierarchie rysů, která umožní, aby byl rys sjednotitelný s jakýmkoli svým dílčím nebo nadřazeným rysem. Výše uvedený algoritmus zajišťuje pouze to, že rysy kategorií sdílené mezi tvary a omezeními lexému jsou unifikovatelné (tj. kompatibilní), což umožňuje další rysy na tvarech, které nejsou uvedeny v omezeních. Přísnější ořezávací algoritmus by mohl zajistit, aby rysy tvaru byly podmnožinou omezení uložených na lexém, ale protože nemáme plnou kontrolu nad rysy jednotlivých tvarů (importovaných z Wikidat), je výše uvedený algoritmus vhodnější.
Všimněte si, že tento očišťovací algoritmus nefunguje dobře s unárními rysy, tj. rysy, které se mohou ve tvaru buď objevit, nebo neobjevit, ale nestojí v opozici k žádnému jinému rysu (např. rys kontrakce, který může označovat kontrahovaná anglická slovesa). Takové rysy nebudou tímto ořezávacím algoritmem ovlivněny, ať už se vyskytují v omezeních nebo na tvarech (protože algoritmus předpokládá, že absence rysu je kompatibilní s jakýmkoli rysem). Pokud je podpora takových rysů vyžadována, měl by být přidán další krok, který ověří, zda jsou takové rysy přítomny nebo chybí jak v omezeních, tak v rysech tvaru.
Ideálně by měl očisťovací algoritmus zachovat přesně jeden tvar lexému. V praxi může zůstat zachováno několik tvarů. Aby se v takových případech důsledně vypisoval méně označený tvar, jsou tvary seřazeny podle kanonického pořadí v tabulce funkce sortForms
. Tato funkce seřadí tvary podle lexikografického pořadí nad kategoriemi a znaky, jak je uvedeno v položce tabulky cannonical_order
. Pokud například u slovesného lexému není zadán žádný rys osoby, projdou procesem ořezávání různé tvary osoby daného lexému, ale třídění zajistí, že se jako první objeví tvar třetí osoby (pokud je mezi původními tvary daného lexému přítomen).
Výstupem této fáze je seznam lexémů, kde každý lexém má ořezaný seznam tvarů kompatibilních s gramatickými omezeními kladenými na lexém. Pokud nejsou ve zbytku pipeline provedeny další úpravy, je první tvar v seznamu ořezaných tvarů tím, který bude použit pro vykreslení textu NLG. (Pokud proces očištění nepřežije žádný tvar, použije se jako záložní tvar lemma lexému). Všimněte si, že vyžádání řetězcového tvaru lexému (pomocí vestavěné funkce tostring
) poskytuje tento první tvar (případně lemma), protože funkce byla redefinována v rámci modulu lexeme
.
Aplikace fonotaktických pravidel (fáze 4)
Během této fáze je třeba uplatnit fonotaktická a ortografická pravidla specifická pro daný jazyk. Obecně platí, že tvary mohou projít fonotaktickou změnou v závislosti na sousedních tvarech (bez ohledu na mezery a prázdné tvary), takže tento proces vyžaduje lineární procházení tvarů a přizpůsobení těch, které by měly být změněny. Fonotaktická varianta každého tvaru může být uložena v existujícím seznamu jejích tvarů, v takovém případě je třeba správný tvar povýšit na první pozici v seznamu tvarů, nebo mohou existovat speciální pravidla měnící existující první tvar seznamu (s využitím funkce lexeme
pomocné funkce modulu replaceByForm
).
Konkrétně se aplikace fonotaktiky provádí pomocí funkce nazvané applyPhonotactics
. Tato funkce je (ve fázi inicializace) vázána na implementaci specifickou pro daný jazyk, kterou lze nalézt v modulu Module:Sandbox/AbstractWikipedia/Phonotactics/xx
(kde xx
znamená kód jazyka). Pokud taková funkce není definována, použije se výchozí funkce no-op.
Jako příklad se můžeme podívat na anglickou a hebrejskou implementaci:
- Anglická implementace prohledává lexémy a hledá neurčitý člen a (identifikovaný pomocí lemmatu a části řeči). Pokud je takový lexém nalezen, je kontrolován následující lexém (bez ohledu na mezery a prázdné tvary), a pokud začíná samohláskou, je tvar členu nahrazen tvarem an. Všimněte si, že v současné implementaci se k určení, zda tvar začíná samohláskou, používá jednoduchý seznam regulárních výrazů. V ideálním případě by tato informace měla být uložena a načtena z Wikidat pro každý lexém.
- Hebrejská implementace se stará o určité ortografické a fonotaktické alternace, ke kterým dochází po hebrejské proklitice. Prohledává seznam lexémů, a pokud je nalezena proklitika identifikovaná svým lemmatem, mohou být následující lexémy pozměněny následujícími způsoby:
- Mezery následující za proklitikou jsou odstraněny.
- Určitý člen (identifikovaný podle části řeči) se odstraňuje po určitých proklitikách.
- Pokud za proklitikem následuje číslo psané číslicemi, přidává se v souladu s hebrejskou ortografickou konvencí pomlčka.
- Pokud za proklitikou následuje slovo začínající na písmeno Vav, toto písmeno se zdvojuje v souladu s hebrejskými pravidly psaní nevokalizovaného textu.
Výstupem této fáze je seznam lexémů, přičemž každý lexém by měl mít jako svůj první tvar tvar kompatibilní s morfosyntaktickými a fonotaktickými omezeními, která jsou na něj kladena.
Sestavení konečného textu (fáze 5)
Sestavení vykreslovaného textu se provádí ve funkci constructText
. Tato funkce v podstatě spojí řetězcovou interpretaci všech lexémů předaných z předchozí fáze. Připomeňme, že řetězcová reprezentace lexému je obvykle první formou v seznamu tvarů, která by měla odpovídat fonotaktickým a morfosyntaktickým omezením. Funkce přitom dbá i na speciální vykreslení mezer a interpunkce a aplikuje potřebnou kapitalizaci. V současné době jsou za tímto účelem implementována následující pravidla:
- Pokud se vyskytnou po sobě jdoucí lexémy
s mezerami
(např. lexémy, které obsahují pouze bílé znaky), zachová se pouze poslední z nich. To je nutné, aby se zabránilo vícenásobným po sobě jdoucím mezerám, které by jinak mohly vzniknout kolem slotů, které byly vyhodnoceny jako prázdný řetězec. - Pokud se vyskytnou po sobě jdoucí koncová interpunkční znaménka (definovaná v tabulce
trailing_punctuation
), zachová se pouze interpunkční znaménko s nejvyšší prioritou (např. tečka potlačí čárku). - Koncová interpunkční znaménka také potlačují všechny předcházející mezery.
- První slovo realizovaného textu, stejně jako každé slovo, které následuje po interpunkčním znaménku vyvolávajícím psaní velkých písmen (jak je definováno v tabulce
capitalization
), se píše s velkým písmenem. To se provádí pomocí funkceucfirst
, která respektuje speciální pravidla pro psaní velkých písmen v jazyce realizace.
Účelem výše uvedených pravidel je umožnit autorům šablon psát šablony co nejpřirozeněji, aniž by příliš přemýšleli o tom, kde mají uvádět mezery nebo interpunkční znaménka. Za většiny okolností tato heuristika zachovává pouze nezbytné mezery a interpunkční znaménka. (Všimněte si, že pokud je potřeba escapovat interpunkční znaménka nebo mezery, aby je nebylo možné zpracovat prostřednictvím této funkce, mohl by je autor šablony zabalit do funkce TemplateText
, který by jim přiřadil typ lexému text
, čímž by se vyhnul jakémukoli dalšímu zpracování tohoto typu).
Výstupem této fáze je textový řetězec odpovídající jedné šabloně nebo konstruktoru.
Renderování celého článku
Protože abstraktní obsah Q-id může obsahovat několik konstruktorů, poslední fáze vykreslování spočívá ve spojení vykreslení jednotlivých konstruktorů. To se v současné době provádí jednoduše tak, že se spojí výstupní řetězce z poslední fáze a v případě potřeby se mezi ně přidá počáteční mezera. Všimněte si, že konstruktory, které nebyly spojeny s žádným rendererem pro daný jazyk, jsou z realizace jednoduše vynechány. To umožňuje částečnou realizaci obsahu pro tyto jazyky namísto selhání celé realizace.
Pokyny pro přispěvatele
Psaní nového abstraktního obsahu
Nový abstraktní obsah můžete vytvořit pro libovolnou položku úpravou modulu AbstractContent. Abstraktní obsah pro každou položku je položka v modulu content
, podle klíče Q-id položky.
Samotná položka je tabulka, která se skládá ze seznamu konstruktorů, které mají být vykresleny v daném pořadí. Každý konstruktor je tabulka, jejíž pole udávají obsah konstruktoru. V současné době neexistují žádné pevné pokyny, jak konstruktory navrhovat, ale obecně by měly být co nejvíce jazykově agnostické. Typicky by hodnoty polí tabulky měly být řetězce, například Q-ids, ale neexistuje žádné tvrdé omezení, protože pole mohou obsahovat i číselné nebo logické hodnoty. Hodnoty polí mohou být zejména dílčími konstruktory, tj. tabulkami.
Jediným požadavkem na konstruktory je, aby obsahovaly pole _predicate
. Toto pole by mělo obsahovat řetězec, který identifikuje typ konstruktoru. Při vykreslování konstruktoru se vyhledá odpovídající renderer se stejným názvem.
Všimněte si, že v rámci modulu Constructors lze také napsat kód v jazyce Lua, který automaticky vytvoří konstruktory pro určité typy položek. Logika výběru správného typu konstruktoru by se měla odehrávat ve funkci Constructors
. Návratovou hodnotou by měl být seznam konstruktorů (případně jeden), jak je uvedeno výše.
Vytváření nových rendererů
Ať už přidáváte renderery do jazyka, který již nějaké definované má, nebo vytváříte nový modul rendererů pro nový jazyk, měly by být renderery umístěny v modulu s názvem Module:Sandbox/AbstractWikipedia/Renderers/xx
kde xx
znamená příslušný kód jazyka. Jazykově neutrální Module:Sandbox/AbstractWikipedia/Renderers
modulu definuje některé obecné užitné funkce pro vyhodnocovací rendereru.
Moduly rendereru pro jednotlivé jazyky exportují tabulku (obvykle s názvem p
), jehož každé pole je rendererem pro určitý typ konstruktoru, s nímž sdílí název (např. renderer Person
je určen k verbalizaci konstruktoru Person
). Každý renderer se skládá ze sady skupin šablon (vysvětleno níže), z nichž jedna se musí jmenovat main
. Pokud je použit renderer (typicky proto, že je třeba verbalizovat konstruktor příslušného typu), je to skupina šablon main
která je verbalizována. Ostatní skupiny šablon lze použít jako interpolace dílčích šablon v rámci šablon main
a mezi sebou navzájem.
Skupina šablon je v podstatě seznam variant šablon. Když je skupina šablon vyhodnocena, je vybrána a verbalizována první varianta šablony v seznamu, jejíž předběžné podmínky jsou splněny. Není-li taková varianta šablony nalezena, je vrácena prázdná šablona, jejímž výsledkem je prázdná verbalizace.
Každá variantní šablona je tabulka se třemi poli:
- Povinné pole
template
obsahuje skutečnou šablonu, která se má verbalizovat pomocí syntaxe šablonovacího jazyka (s některými doplňky vysvětlenými níže). - Nepovinné pole
validity
obsahuje seznam polí konstruktoru, která musí být přítomna, aby se tato varianta realizovala. Všimněte si, že všechny šablony v rendereru mají přístup k polím realizovaného konstruktoru (použitelné jako argumenty interpolace). - Nepovinné pole
roles
obsahuje seznam možných gramatických rolí, kterým tato varianta odpovídá. V podstatě se jedná o podmínku na označení závislosti, která se používá spolu s voláním podšablony nebo příslušného konstruktoru. Pouze pokud tento štítek odpovídá jedné z rolí uvedených v seznamuroles
, bude šablona vyhodnocena. Prázdný řetězec""
může odpovídat absenci štítku. - V budoucnu může být přidáno další pole
conditions
, které umožní vyhodnotit libovolný podmíněný výraz na polích konstruktoru.
Odlišnosti od kanonické syntaxe šablony
Jak bylo uvedeno výše, implementace umožňuje mírně rozšířenou syntaxi ve srovnání se syntaxí uvedenou v dokumentu syntaxe šablonovacího jazyka. Tato rozšíření zahrnují následující:
- L-id (L následované číslicemi) a Q-id (Q následované číslicemi) lze použít bez uvozovek. Pokud slouží jako parametry funkce, jsou převedeny na řetězce. Pokud se vyskytují samostatně v rámci slotů (buď jako literály, nebo jako interpolované parametry), slouží jako zkrácený zápis pro volání šablonových funkcí
Lexeme(L-id)
aLabel(Q-id)
. - Podobně čísla, která se objevují samostatně ve slotech (buď jako literály, nebo jako interpolované parametry), slouží jako zkratka pro volání funkce šablony
Cardinal(číslo)
. - Parametry interpolace mohou obsahovat řetězce, které lze interpretovat jako šablony (protože obsahují slot: nějaký text uzavřený
{ }
). V takovém případě je argument interpolace vyhodnocen jako dílčí šablona (která má přístup ke stejným parametrům jako vyvolávající šablona). - Podobně mohou parametry interpolace obsahovat skupiny šablon (jak je definováno výše). K tomu dochází zejména v rámci definice rendererů. V tomto případě interpolace spočívá ve výběru vhodné varianty šablony a její vyhodnocení.
- V tomto prototypu není implementováno rozšíření o podmíněné funkce.
- Protože jsou šablony definovány v kódu Lua, lze za ně přidávat komentáře pomocí standardní syntaxe komentářů Lua.
Implementace nových relačních funkcí
Při vytváření obsahu pro nový jazyk je pravděpodobné, že bude třeba definovat nové závislostní vztahy nebo upravit stávající definice. Aby závislostní vztah, použitý v šablonách, měl vliv na verbalizaci, musí mít definovanou odpovídající relační funkci. Taková funkce může být definována buď pro všechny jazyky v Module:Sandbox/AbstractWikipedia/Relations, nebo pro konkrétní jazyk Module:Sandbox/AbstractWikipedia/Relations/xx
, kde xx
znamená kód jazyka.
Obecně je vhodné definovat vztahy pokud možno nezávisle na jazyce. Víme například, že v mnoha jazycích existuje shoda podmětu se slovesem, ale přesné rysy, které se shodují, se mohou v jednotlivých jazycích lišit. Namísto toho, abychom pro každou kombinaci rysů definovali implementaci specifickou pro daný jazyk, můžeme definovat jazykově nezávislou implementaci relační funkce subj
, která vynucuje podobu vlastností person
, number
, a gender
a přiřadí subjektu pád nominativ
. Taková funkce by fungovala i v jazycích, které vykazují pouze část těchto shodných rysů nebo nemají pádovou morfologii, protože irelevantní operace by se staly operacemi bez opce. V některých případech je však zapotřebí implementace specifická pro daný jazyk; například pokud bychom chtěli přiřadit např. pád ergativ
k subjektu, vyžadovalo by to samostatnou implementaci funkce relace specifickou pro daný jazyk. (Bylo by také možné seskupit jazyky do jazykové hierarchie, v níž by podobné jazyky sdílely implementace, ale to nebylo v prototypu provedeno.)
Pro správnou funkci pipeline NLG by měly být relační funkce vždy definovány se dvěma vstupními parametry, source
a target
, odpovídající slotům, na kterých relace působí. Kromě toho by tělo funkce mělo v zásadě používat pouze čtyři operace, aby bylo zajištěno, že pořadí použití relací není podstatné (prefix l
odkazuje na modul Lexemes):
verifyPos(slot, slovní druh)
: to umožňuje kontrolu správnosti, zda je příslušný slot lexémem se slovním druhem, který lze s daným lexémem sjednotit. Nejčastěji je však tato kontrola v praxi příliš omezující, takže se jí lze vyhnout. V principu by slovní druhy mohly být uspořádány do typové hierarchie, která by umožnila pružnější typovou kontrolu, ale to zatím nebylo v prototypu implementováno.l.unifyFeatures(kategorie ke sjednocení, zdroj, cíl)
: sjednotí vlastnosti dané kategorie zdrojového a cílového slotu.l.unifyFeatures(source-category-to-unify, zdroj, cíl, target-category-to-unify)
: sjednotí rys zdrojové kategorie s rysem cílové kategorie.l.unifyWithFeature(kategorie, slot, vlastnost)
: Sjednotí funkci uvedenou v kategorii slotu s předanou funkcí. Pokud slot takovou kategorii ještě nemá, znamená to přiřazení nové funkce do kategorie daného slotu.
Všimněte si, že kterákoli z těchto funkcí může selhat, pokud dané funkce nejsou sjednotitelné, což vede k selhání celé realizace (s příslušným chybovým hlášením).
Implementace nových funkcí pro použití v šablonách
Klíčovou součástí šablonovacího jazyka je možnost volat funkce, které mají proměnný počet argumentů, v rámci slotů. Jazykově nezávislé implementace těchto funkcí jsou definovány v rámci modulu Module:Sandbox/AbstractWikipedia/Functions, zatímco implementace pro jednotlivé jazyky jsou definovány v Module:Sandbox/AbstractWikipedia/Functions/xx
, kde xx
znamená kód jazyka. Funkce jsou definovány jako všechny ostatní exportované funkce modulu Scribunto.
Funkce, které jsou volány na úrovni slotu (na rozdíl od těch, které jsou volány jako argumenty jiných funkcí), musí vracet jeden lexém nebo seznam lexémů. Pro snadnou konstrukci datového typu lexém
modul Lexemes se hodí (importován jako l
). Mnoho definovaných funkcí získává data z Wikidat; k tomuto účelu mohou použít pomocný modul Wikidata (importovaný jako wd
).
Obecně platí, že jazykově specifické implementace jsou potřebné, pokud funkce potřebuje přistupovat k lexému specifickému pro daný jazyk Wikidat nebo modelovat některé jazykově specifické jevy. Kdykoli je to možné, je lepší psát funkce nezávislé na jazyce. Všimněte si, že mohou sloužit jako záložní implementace, které pak mohou být přepsány jazykově specifickou implementací.
Některé z definovaných funkcí jsou pro správnou funkci systému nezbytné: jedná se o tyto funkce TemplateText
, Lexeme
, Label
a Cardinal
, které jsou implicitně vyvolány pro určité prvky jazyka šablon, jak je vysvětleno výše.
Zápis funkcí jako dílčích šablon
Obecně platí, že psaní funkcí v těchto modulech vyžaduje určitou znalost programování v jazyce Lua. Je však možné a vhodné definovat funkce, které jsou pouhým vyhodnocením dílčích šablon, přičemž argumenty funkce jsou vázány na argumenty šablony. Jako příklad lze uvést práci funkce QuantifiedNoun
, která je definována zde:
function p.QuantifiedNoun(num, noun) return te.evaluateTemplate("{nummod:Cardinal(num)} {root:noun}", { num = num, noun = noun}) end
Podšablona, která se má použít, se zadává jako první parametr příkazu funkce evaluateTemplate
(prefix te
znamená modul TemplateEvaluater, importovaný z modulu Functions). Tato šablona se řídí běžnou syntaxí šablonovacího jazyka, včetně použití slotů, relací, volání funkcí a parametrů interpolace. Takto definovaná šablona má však přístup pouze k interpolačním parametrům, které jsou definovány v tabulce předané jako druhý parametr příkazu evaluateTemplate
(konkrétně { num = num, noun = noun}
v příkladu). Jak vidíte, jedná se o jednoduché vazby argumentů funkce na jména interpolačních argumentů (obvykle se stejným jménem, ale není to nutné). Při použití tohoto typu definice funkce vlastně nemusíte umět programovat v jazyce Lua, protože funkci můžete definovat většinou pomocí syntaxe jazyka šablon.