LISP: Otázky a odpovede

Otázky môžete posielať elektronicky na adresu bielik [zavináč] fiit[.]stuba[.]sk.

Zoznam otázok podľa okruhov

LispWorks

Ako získať LispWorks?

LispWorks 6.1 Personal Edition sa dá sa voľne stiahnuť na adrese ftp://ftp.lispworks.com/pub/software_tools/downloads/lwper601/x86-win32/LispWorksPersonal60_Installer.exe (Windows). Na stránke softvéru nájdete aj verzie pre platformy Macintosh, Linux, FreeBSD, Solaris (je potrebné vyplniť krátky registračný formulár).

Odporúčam stiahnuť aj dokumentáciu.

Odpovedal: Martin Labaj

to Homepage to Teaching to FLP to the Top

Ako spustiť LispWorks v CPU?

V CPU sú dva druhy počítačov: s operačným systémom Win XP a s Windows 7.

Na Windows 7 strojoch sa dá LispWorks spustiť cez Start menu (všimnite si číslo verzie, v Start menu mohli ostať odkazy na staršiu verziu prostredia):

	Start \ All Programs \ LispWorks 6.0 Personal \ LispWorks

Na Windows XP strojoch sa dá LispWorks spustiť pomocou ikony na ploche. (V Start menu LispWorks pravdepodobne nie je). Je to symbolický odkaz na spustiteľný súbor:

	C:\Program Files\Harlequin\LispWorks Personal\lispworks-personal-4100.exe

Odpovedal: Matej Košík a Martin Labaj

to Homepage to Teaching to FLP to the Top

Ako pracovať v LispWorks?

Opíšeme postup v niekoľkých krokoch:

  1. Spustíme Lispworks.
  2. Najskôr treba vytvoriť lispovský program. Môžete to urobiť vo vašom obľúbenom editore alebo v editore LispWorks, spustíme ho výberom Tools-Editor (ten je výhodný kvôli znázorňovaniu zátvoriek). Nezabudnite si súbor uložiť!
  3. Načítame vytvorený program voľbou Load z menu (je výhodné nastaviť, aby nástrojová lišta bola na každom okne - vyberte Tools - Preferences... a v Environment - General - Window Options vyberte Separate windows with menu bars).
  4. Ak interpret úspešne načítal program, môžeme zadávať lispu formy (v okne Listener). Lisp formu prečíta, vyhodnotí a vypíše výsledok.
  5. V prípade problémov nasleduje proces ladenia.

Odpovedala: Mária Bieliková a Martin Labaj

to Homepage to Teaching to FLP to the Top

Aké sú klávesové skratky v LispWorks?

Spočiatku sa môže zdať neintuitívne, že v prostredí LispWorks nefungujú bežné klávesové skratky známe z iných prostredí (napr. kopírovanie a vkladanie pomocou Ctrl+C, Ctrl+V) alebo interpretov (napr. šípka hore pre zopakovanie predchádzajúceho príkazu). Je to tým, že prostredie LispWorks emuluje editor Emacs. Vyberáme niekoľko užitočných príkazov:

Interpret (okno Listener):

  • Ctrl-C, Ctrl-P alebo Esc, P (previous) - nahradí celý aktuálny príkaz predchádzajúcim príkazom v histórii (napr. práve sme vyhodnotili formu a chceme ju upraviť a vyhodnotiť znova).
  • Ctrl-C, Ctrl-N alebo Esc, N (next) - nahradí aktuálny príkaz nasledujúcim príkazom v histórii.
  • Ctrl-C, Ctrl-Y (yank) - vloží predchádzajúci príkaz na miesto kurzora (napr. práve sme vyhodnotili formu a chceme ju použiť vo vnútri inej formy - začneme písať novú formu a na vhodnom mieste použijeme túto skratku).
  • Ctrl-C, Ctrl-K (kill) - vymazanie aktuálneho príkazu.

Editor/interpret:

  • Ctrl-W - vystrihnutie označeného textu do schránky.
  • Alt-W - skopírovanie označeného textu do schránky.
  • Ctrl-Y - vloženie textu zo schránky.
  • Ctrl-medzera - začatie označovania textu od pozície kurzora.
  • Ctrl-G - zrušenie označovania textu.
  • Príklad: stlačíme Ctrl-medzera, pomocou šípiek označíme text a následne priamo stlačením Alt-W text skopírujeme alebo stlačením Ctrl-G označenie zrušíme. Na označenie môžeme tiež použiť myš bežným spôsobom.

Prehľad príkazov pre prácu s históriou nájdete v tejto časti dokumentácie LispWorks. Rýchly prehľad príkazov editora Emacs nájdete napríklad na tejto stránke.

Môžeme tiež nastaviť používanie klávesov podľa Microsoft Windows (v Tools - Preferences... v časti Environment - Emulation - Keys vyberte Editor keys like Microsoft Windows, menu bar via Alt key).

Odpovedal: Martin Labaj

to Homepage to Teaching to FLP to the Top

Ako ladiť programy v LispWorks?

V prvom rade si treba všimnúť, čo poskytuje interpret lispu.

Znak CL-USER 1> označuje vrchnú úroveň interpretu (číslo označuje krok, resp. poradie vyhodnocovanej formy). V prípade prerušenia vyhodnotenia výpočtu sa interpret dostáva do nasledujúcej úrovne označenej číslom (číslo označuje hĺbku vnorenia):

* A

Error: The variable A is unbound.
  1 (continue) Try evaluating A again.
  2 Return the value of :A instead.
  3 Specify a value to use this time instead of evaluating A.
  4 Specify a value to set A to.
  5 (abort) Return to level 0.
  6 Return to top loop level 0.

Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.

CL-USER 2 : 1 >

Na vrchnú úroveň sa v tomto prípade dostaneme zadaním voľby :c 5 alebo :c 6. Interpret vždy vypíše možnosti relevantné konkrétnej situácii.

Pri ladení odporúčame použiť tieto funkcie:

  • TRACE, argumentom je meno funkcie (bez apostrofu), pri vyhodnocovaní tejto funkcie sa potom vypíše každé volanie funkcie spolu s parametrami a aj výsledkom, hodí sa pre ladenie rekurzívnych funkcií, použitím UNTRACE toto správanie zrušíme
  • STEP, používa sa v tvare (STEP forma), kde forma predstavuje výraz, ktorý chceme vyhodnotiť tak, že budeme sledovať vyhodnocovanie po krokoch. Po napísaní :help sa zobrazí help (príkazy, pomocou ktorých postupujeme po jednotlivých krokoch alebo sa zaujímame iba o hodnotu uvedenej formy bez krokovania a pod.
    Vyskúšajte si to!!!!
        CL-USER 2 > (STEP (PRINT '5))
        (PRINT (QUOTE 5)) -> :help
    
    	:s        Step this form and all of its subforms (optional +ve integer arg)
    	:st       Step this form without stepping its subforms
    	:si  	  Step this form without stepping its arguments if it is a function call
    	:su       Step up out of this form without stepping its subforms
    	:sr	      Return a value to use for this form
    	:sq       Quit from the current stepper level
    	:bug-form <subject> &key <filename>
    	         Print out a bug report form, optionally to a file.
    	:get <variable> <command identifier>
    	         Get a previous command (found by its number or a symbol/subform within it) and put it in a variable.
    	:help    Produce this list.
        :his &optional <n1> <n2>
    	         List the command history, optionally the last n1 or range n1 to n2.
    	:redo &optional <command identifier>
    	         Redo a previous command, found by its number or a symbol/subform within it.
    	:use <new form> <old form> &optional <command identifier>
    	         Do variant of a previous command, replacing old symbol/subform with new symbol/subform.
    	NIL
    	(PRINT (QUOTE 5)) -> :s
    	   (QUOTE 5) -> :s
    	   5
    	5
    	5
    	5
    
    Prvá hodnota 5 je výsledkom vyhodnotenia formy (QUOTE 5). Ďalšia je výpisom (formou PRINT), nasledujúca hodnotou formy PRINT a nakoniec posledná je hodnotou formy STEP.

V prípade odhalenia problému, v editore opravíme a doplníme program, opravené alebo doplnené funkcie načítame (položka Load z menu editora).

Odpovedala: Mária Bieliková a Marek Trabalka

to Homepage to Teaching to FLP to the Top

Clisp (ANSI CL)

Ako získať Clisp?

CLisp je voľne dostupný v balíčkovacích systémoch rôznych platforiem. Napr. na Linux distribúcii Debian (a odvodených: Ubuntu, Mint, ...) stačí použiť príkaz

sudo apt-get install clisp

V prípade Linux distribúcie Red Hat/Fedora/CentOS použijeme príkaz

sudo yum install clisp

Lisp spustíme príkazom

> clisp

Odpovedal: Katarína Uherková

to Homepage to Teaching to FLP to the Top

Ako pracovať v Clispe?

Vytvoríme program v ľubovoľnom editore, uložíme ho ako meno.lsp. Na konzole spustíme lisp príkazom

> clisp
v adresári, kde je uložený vytvorený program. V lispe načítame program takto
[1]>(compile-file 'meno)
[2]>(load 'meno)

Funkciou compile-file vytvorí lisp súbor meno.fas. Funkcia load zabezpečí načítanie vytvoreného súboru.

Ak sa v programe vyskytli chyby, lisp ich vypíše pri kompilácii a nevytvorí súbor meno.fas. Ak použijeme iba funkciu load, vstupom bude súbor meno.lsp. Lisp vypíše formy, v ktorých bola chyba. Po úspešnom načítaní súboru môžeme používať zadefinované funkcie.

Help vyvoláme príkazom

>:h

Help sa mení podľa toho, v ktorom stupni vyhodnocovania sa lisp nachádza. Vypisuje iba tie príkazy, ktoré možno v tomto kroku využiť.

Prácu ukončíme pomocou funkcie exit alebo quit

>(exit)
alebo
>(quit)

Odpovedala: Katarína Uherková

to Homepage to Teaching to FLP to the Top

Ako ladiť program v Clispe?

Na ladenie môžeme použiť funkcie trace a step.

Použitie funkcie trace:

>(trace meno_funkcie)
T
Teraz pre každé volanie funkcie bude lisp vypisovať vstupy a výstup.
>(meno_funkcie argumenty)
Na zrušenie výpisov použijeme funkciu
>(untrace meno_funkcie)

Funkcia step umožní vyhodnocovať formu po krokoch.

Použitie funkcie step:

>(step (meno_funkcie argumenty))
Teraz môžeme použiť príkazy
:c - vyhodnotenie celého výrazu naraz
:s - vnorenie sa pri vyhodnotení formy
:n - vyhodnotenie formy bez vnorenia
:o - vyhodnotenie foriem až po predchádzajúcu úroveň
:h - zobrazí podrobne aj ostatne príkazy

Odpovedala: Katarína Uherková

to Homepage to Teaching to FLP to the Top

GCLisp 1.01

Ako spustiť GC Lisp?

Aby ste mohli pracovať v GCLispe, treba mať v pracovnom adresári minimálne súbory: gc.bat, config.lsp a init.lsp.

  • Súbor gc.bat obsahuje volanie samotného GCLispu (gclisp.exe) - nachádza sa tu cesta k tomuto súboru.
  • Súbor config.lsp obsahuje nastavenia ciest pre systémové premenné, treba skontrolovať či cesty zodpovedajú skutočnému unmiestneniu GCLispu.
  • Súbor init.lsp slúži na inicializáciu GClispu (načíta aj config.lsp). Netreba v ňom nič meniť.

GCLisp spustíme súborom gc.bat alebo priamo gclisp.exe. Odporúčame spúšťanie GCLispu z pracovného adresára (kde sú alebo budú uložené vytvorené lispovské programy) a nie z miesta, kde sa nachádza samotný systém.

Odpovedala: Mária Bieliková

to Homepage to Teaching to FLP to the Top

Ako pracovať v GC Lispe?

Opíšeme postup v niekoľkých krokoch:

  1. Najskôr treba vytvoriť lispovský program. Môžete to urobiť vo vašom obľúbenom editore.
  2. Ďalším krokom je spustenie GClispu.
  3. Načítame vytvorený program pomocou funkcie LOAD:
    * (LOAD "pokus.lsp")
    Predpokladáme, že vytvorený súbor má meno pokus.lsp a nachádza sa v adresári, z ktorého sme spustili GCLisp.
    Ak má súbor príponu lsp, netreba ju písať:
    * (LOAD 'pokus)
    V prípade, že sa súbor nachádza na inom mieste, treba uviesť celú cestu (treba použiť dvojité lomítka //).
  4. Ak interpret GCLispu úspešne načítal program, môžeme zadávať lispu formy. Lisp formu prečíta, vyhodnotí a vypíše výsledok.
  5. V prípade problémov nasleduje proces ladenia.
  6. Prácu ukončíme pomocou funkcie EXIT. Funkcia nemá žiadne argumenty. Nezabudnite, že ide o funkciu a teda, ak chceme, aby ju lisp vyhodnotil treba ju uviesť ako zoznam (v zátvorkách):
    * (EXIT)

Odpovedala: Mária Bieliková

to Homepage to Teaching to FLP to the Top

Ako editovať program v GC Lispe?

V GCLispe možno použiť priamo GMACS editor. Je to výhodné najmä pri ladení programov.
GMACS editor spustíme z prostredia interpretu GCLispu stlačením Ctrl-E. Späť to interpretu lispu sa dostaneme stlačením klávesu F1. Niektoré užitočné funkcie GMACS editora:

  • F1 - exit
  • F2 - help
  • F3 - select (or create) buffer
  • F4 - switch between last two buffers
  • F5 - list of buffers
  • F6 - directory pathname
  • F7 - find file
  • F8 - read file
  • F9 - save with default name
  • F10 - save with specified name

Užitočná je kombinácia kláves Alt - 1. Ak stalčíme tieto dve klávesy v prípade, že kurzor je nastavený na prvý znak (spravidla otváracia zátvorka) formy, ktorú sme modifikovali, táto sa vyhodnotí (netreba po návrate do interpretu lispu použiť funkciu LOAD.

Odpovedala: Mária Bieliková

to Homepage to Teaching to FLP to the Top

Ako ladiť program v GC Lispe?

V prvom rade si treba všimnúť, čo poskytuje interpret GCLispu. Po spustení interpretu sa vypíše:

Type Alt-H for Help
Top-Level
*
Help (klávesy Alt-H) vypíše základné možnosti, napr. ako spustiť GMACS editor Ctrl-E, ako zistiť klávesové skratky Alt-K, ako sprístupniť dokumentáciu Alt-D a pod.

Znak * označuje vrchnú úroveň interpretu (top level). V prípade prerušenia vyhodnotenia výpočtu sa interpret dostáva do nasledujúcej úrovne označenej číslom (číslo označuje hĺbku vnorenia):

* A

ERROR:
Unbound variable
1>

Na vrchnú úroveň sa dostaneme stlačením klávesov Ctrl-C (pozri zoznam kľúčov pomocou kláves Alt-K).

Pri ladení odporúčame použiť tieto funkcie:

  • TRACE, argumentom je meno funkcie (bez apostrofu), pri vyhodnocovaní tejto funkcie sa potom vypíše každé volanie funkcie spolu s parametrami a aj výsledkom, hodí sa pre ladenie rekurzívnych funkcií, použitím UNTRACE toto správanie zrušíme
  • STEP, používa sa v tvare (STEP forma), kde forma predstavuje výraz, ktorý chceme vyhodnotiť tak, že budeme sledovať vyhodnocovanie po krokoch. Po stlačení klávesu ENTER sa zobrazí help (príkazy, pomocou ktorých postupujeme po jednotlivých krokoch, alebo sa zaujímame iba o hodnotu uvedenej formy bez krokovania a pod.
    Vyskúšajte si to!!!!
    * (STEP (PRIENIK '(A B C) '(C D)))

V prípade odhalenia problému, v editore (Ctrl-E) opravíme a doplníme program, opravené alebo doplnené funkcie načítame (Alt-1 v editore alebo funkciou LOAD v interprete lispu).

Odpovedala: Mária Bieliková

to Homepage to Teaching to FLP to the Top

Ako zistiť v GC Lispe, či interpret danú funkciu načítal v poriadku?

Nevýhodou interpretu GCLispu 1.01 je, že pri načítavaní programu, v ktorom je syntaktická chyba sa nedozvieme, kde táto chyba nastala.

Funkčnú väzbu symbolu zistíme pomocou funkcie SYMBOL-FUNCTION. V prípade, že LAMBDA výraz je zložitý, vypíše sa iba jeho hlavná štruktúra. Ak chceme vidieť celú definíciu funkcie, použijeme na výpis funkciu PPRINT (pretty print):

* (PPRINT (SYMBOL-FUNCTION 'PRIENIK))
Výhodné je zadefinovať si funkciu - skratku, pre vyššie definovanú úlohu:
(DEFUN PP (SYMBF)
   (PPRINT (SYMBOL-FUNCTION SUMBF))
)

Odpovedala: Mária Bieliková

to Homepage to Teaching to FLP to the Top

Lispovský program a funkcie

Čo všetko by malo byť v lispovskom programe?

Lispovský program pozostáva v prvom rade z definícií funkcií, ktoré potom použijeme pri zostavovaní foriem (vyhodnotiteľných výrazov).
Funkcie sa definujú formou DEFUN.

Ďalej v programe môžu byť ľubovoľné ďalšie formy. Tieto sa vyhodnotia pri načítaní programu do interpretu lispu (LOAD). Často program okrem definícií funkcií obsahuje priradenia hodnôt premenným (SET, SETQ), ktoré sa potom výhodne použijú pri testovaní.
Ak napr. definujeme funkciu, ktorá pracuje s poľom reprezentovaným zoznamom riadkov poľa, je výhodné definovať testovacie vstupy v programe:

(SETQ A '((1 2 3) (4 5 6) (7 8 9) (10 11 12)) )
a potom ich použiť pri ladení:
* (VYBER 2 3 A)

Odpovedala: Mária Bieliková

to Homepage to Teaching to FLP to the Top

Aké argumenty vyžaduje CONS v Common lispe?

Funkcia CONS vyžaduje v Common lispe ako argumenty dva ľubovoľné s-výrazy. V prípade, že druhým argumentom je zoznam, výsledkom aplikácie funkcie CONS je tiež zoznam.

CONS vytvorí bodka-dvojicu, ktorej ľavá časť predstavuje prvý argument a pravá časť druhý argument. Prvý argument teda môže byť samozrejme aj zoznam alebo bodka-dvojica.

Vieme, že zoznam je taká bodka-dvojica, ktorej pravá časť je zoznam a tiež, že NIL je zoznam. Ak teda druhý argument funkcie CONS bude zoznam, nezávisle od hodnoty prvého argumentu bude výsledok zoznam.

Vyskúšajte si to!!!!
A nezabudnite si vyskúšať aj ďalšie konštruktory LIST a APPEND.

Odpovedala: Mária Bieliková

to Homepage to Teaching to FLP to the Top

Rekurzívne funkcie

Ako stanoviť ukončovaciu podmienku rekurzie, keď na vstupe funkcie má byť podľa špecifikácie zoznam čísel?

V nižšie definovanej funkcii, ktorá zisťuje, či na vstupe dostala zoznam čísel, sa tvrdí, že prázdny zoznam je zoznamom čísel. Nemala by sa rekurzia zastaviť radšej na jednoprvkovom zozname?

(DEFUN ZOZNAM-CISEL (ZOZ)
  (COND ((NULL ZOZ) T)              ;prázdny zoznam -> úspech
        ((NUMBERP (FIRST ZOZ)) (ZOZNAM-CISEL (REST ZOZ)))
        (T NIL) ))                  ;nepovinné, COND má štandardne toto ukončenie,
                                    ;zápis je však prehľadnejší
Najprv si musíme uvedomiť, aká je presná špecifikácia tejto funkcie. Na prvý pohľad by sa mohlo zdať, že špecifikácia znie takto:

“Ak je na vstupe zoznam čísel, výsledok je T, inak je výsledok NIL.”

To však vôbec nie je pravda. Skúste dať na vstup tejto funkcie niečo iné ako zoznam, napríklad samotné číslo, a dostanete chybové hlásenie o havarovaní funkcie. Napriek tomu je to dobrá a často používaná funkcia, len pracuje podľa mierne odlišnej špecifikácie:

"Vstupom tejto funkcie je zoznam. Ak tento zoznam obsahuje len čísla, výsledok je T, ak zoznam obsahuje aj niečo iné, výsledok je NIL."

(Táto špecifikácia nám výrazne zníži rôznorodosť vstupov, ktoré musí funkcia spracovať. Tým sa tiež zníži množstvo testov, ktoré by musela funkcia v každom rekurzívnom kroku vykonať a funkcia pracuje omnoho rýchlejšie. Funkcia je príliš elementárna na to, aby sa trápila s nejakou podrobnou kontrolou vstupu.)

Nová špecifikácia hovorí, že vstupom je zoznam, teda aj prázdny zoznam, nehovorí však, aký má byť vtedy výstup. Znamená to, že túto hodnotu je možné si dodefinovať ako uznáme za vhodné.

Na to, aby sme si ju zadefinovali ako T, máme tri dobré dôvody:

  1. Zoznam čísel môžeme chápať ako zoznam nejakého typu (numeric). Každý prázdny zoznam v Lispe môže byť naplnený číslami, teda môže byť chápaný ako zoznam čísel, ktorý je len v danom momente prázdny.
  2. Každá funkcia, ktorá bude nadväzovať na túto funkciu a spracovávať rovnaký zoznam, bude tiež rekurzívna (alebo iteratívna), t.j. schopná spracovať aj prázdny vstup (prázdny zoznam).
  3. Ušetríme jeden test a zjednodušíme zápis funkcie.

Odpovedal: Ivan Kapustík

to Homepage to Teaching to FLP to the Top

Ako konštruovať zoznam v rekurzívnej funkcii?

V nižšie definovanej funkcii ZJEDNOTENIE, ktorá spojí dva zoznamy neopakujúcich sa prvkov tak, aby sa ani vo výslednom zozname neopakovali prvky (na najvyššej úrovni) sa použila na konštrukciu zoznamu v rekurzívnom volaní forma:
(CONS (FIRST MN1) (ZJEDNOTENIE (REST MN1) MN2).
Nemohla by sa radšej použiť forma:
(ZJEDNOTENIE (REST MN1) (CONS (FIRST MN1) MN2)?

(DEFUN ZJEDNOTENIE (MN1  MN2)
  (COND ((NULL MN1)                           ;prázdny prvý zoznam ->
                    MN2)                      ;    výsledok spojenia je druhý zoznam
        ((MEMBER (FIRST ZOZ) MN2)             ;prvý prvok MN1 sa vo výsledku neuvažuje
              (ZJEDNOTENIE (REST MN1) MN2) )
        (T  (CONS (FIRST MN1)		      ;prvý prvok MN1 sa vo výsledku uvažuje
                  (ZJEDNOTENIE (REST MN1) MN2)) )))
Obidve možnosti sú z pohľadu špecifikácie funkcie správne. Správanie funkcie navonok (ČO je výstupom funkcie) bude v obidvoch prípadoch rovnaké. Postup vyhodnocovania však rovnaký nebude:
  • Riešenie, ktoré sa uvádza vo funkcii vyššie (najskôr sa rekurzívne vyhodnotí funkcia ZJEDNOTENIE a k výsledku sa pomocou funkcie CONS pripojí prvý prvok zo zoznamu MN1) vedie k rekurzívnej funkcii s rozšírením a nedá sa priamo prepísať na iteráciu. Riešenie s prehodeným poradím aplikácie funkcií CONS a ZJEDNOTENIE vedie k rekurzii na chvoste, čo môžeme považovať za výhodu.
  • V prvej verzii sa však v každom rekurzívnom kroku žiadny z argumentov "nezväčšuje" (počet prvkov prvého sa vždy zmenší o 1 a druhý ostáva nezmenený). Preto podmienka v druhej vetve (MEMBER) sa vždy vykonáva so zoznamom s rovnakým počtom prvkov. Toto nemôžeme povedať o druhej verzii, kde sa počet prvkov druhého argumentu v rekurzívnom volaní postupne rozširuje o prvky z prvého zoznamu, ktoré sa v ňom ešte nenachádzajú. Tým sa zvyšujú nároky na test v druhej podmienke.

V prípade, že sa nepožaduje vytvorenie funkcie s rekurziou na chvoste, odporúčam použiť prvú verziu (t.j. verziu, kde sa počet prvkov zoznamov - argumentov v jednotlivých rekurzívnych volaniach nezväčšuje).

Odpovedala: Mária Bieliková

to Homepage to Teaching to FLP to the Top

Home
Research
Projects
Publications
Books
SCM
Teaching
Links
Last updated:
Mária Bieliková bielik [zavináč] fiit-dot-stuba-dot-sk
Design © 2oo1 KoXo