A Factor nyelvben - mint oly sok minden más is, a kivételkezelés is csupán egy külső modulként létező dolog. Az egész rendszer az állapotfolytatások fölé felépített struktúra, mely a szokásos és néhány esetleg érdekesnek mondható kivételkezelési mechanizmust ad a kezünkbe.
A Factor nyelvben tetszőleges objektumot eldobhatunk kivételként, tehát gyakorlatilag nagyon szabad kezet kapunk kivételek deklarációjához, ami nem biztos, hogy mindig egészséges, gondoljunk arra, hogy dobhatunk például egy olyan kivételt, mely csupán a 42-es fixnum típusú számot fogja kiváltani (ami ugye nem túl informatív dolog). Kivételt a throw szóval válthatunk ki, mely a stack tetején lévő elemet dobja el kivételként!
Példa: kivételek dobása
Az előbb említett problémát elkerülendő, a nyelvben lehetőségünk van egy külön, a kivételek deklarációjához használható eszközt alkalmaznunk az ERROR: szó segítségével. Az ERROR: szót egy azonosító kell hogy kövesse, ez lesz a kivétel neve. Ha így definiáljuk a kivételünket, akkor nem csak egy típust deklarálunk, mely eldobható lesz (a kivétel neve lesz ennek a neve is), hanem egy szó is deklarálódik számunkra, melyet leírva automatikusan kiváltódik a kivételünk. A nyelvi konvenció az, hogy ahol csak lehet, inkább ERROR:-t használjunk mindenféle egyéb objektumok eldobása helyett!
Példa: ERROR: használata
Persze a fentiek mit sem érnének, ha nem lehetne a kivételeket valahol elkapni. Kivételek kezelésére alapvetően kétféle módunk van: recover és cleanup.
A "recover ( ..a try: ( ..a -- ..b ) recovery: ( ..a error -- ..b ) -- ..b )" szó a hagyományos try-catch szemantikának felel meg. Elkapja a kivételt ha az a try kódidézetben fellép és végrehajtja a recover-ként megadott kódidézetet, a kivétel pedig nem terjed tovább.
A "cleanup ( try cleanup-always cleanup-error -- )" szó szemantikája az előzőtől abban tér el, hogy a try kódidézetben történő kivétel fellépése esetén, először a cleanup-always, majd a cleanup-error kódidézetek futnak le és a kivétel továbbterjed a hívóhoz. Emellett a cleanup-always kódidézet, mint azt a neve is sugallja, akkor is le fog futni, ha a try kódidézet futása során nem lépett fel kivétel. Gyakorlatilag a cleanup-always kicsit a más nyelvekben megtalálható finally blokkra hajaz.
A fent látható, akár szokványosnak is mondható megoldásokon túl lehetőségünk van újrakezdésekkel ellátott és folytatható kivételek használatára is.
Egy újraindítható kivétel, ha kiterjed a programon kívülre, a felhasználótól kérdezi meg, hogy mit tegyen, gyakorlatilag ráterheli a kivétel lekezelését. Természetesen azonban nem kérhetjük meg a felhasználót, hogy írja újra a programot, amíg az áll a megfelelő helyen, viszont alternatívákat biztosíthatunk neki, amikből választhat. A program a választott alternatívához rendelt kód futásával folytatódik. A folytatható kivételek az előbbitől csak abban különböznek, hogy itt nem adhatunk meg különféle alternatívákat. A felhasználó csupán annyit választhat, tényleg akarja-e a veszélyes műveletet elvégezni vagy sem!
Példák szofisztikált kivételekre:
Az itt felsoroltak több esetben még tovább kombinálhatóak (pl. rethrow-restart), de az úgy kapott szavak szemantikája világos kell legyen a fentiek alapján...
Említettük, hogy a kivételkezelés csupán egy különálló library és nem is alapvető nyelvi elem. A kivételkezelés fent látható struktúrái, alacsanyabb szintű elemekből építkeznek, úgynevezett állapotfolytatásokból (continuitations).
Az állapotfolytatások a kiszámítás jövőjét jelképezik, felhasználásukkal/hívásukkal olyan hatást érhetünk el, mintha az állapotfolytatás lementésének a pillanatában meglévő maradékprogramot kezdenénk el futtatni. Az állapotfolytatás lementésére a callcc0, míg a folytatására a continue szavak állnak a rendelkezésünkre. Bár ezeknek van további több variációja is kényelmi okokból (pl. callcc1 és continue-with), ezeket itt most különösebben nem részletezzük, mert a szemléletük analóg-jellegű az itt leírtakkal.
A "callcc0 ( quot -- )" szó, egy kódidézetet vár a verem tetejéről input paraméterként. Mielőtt azonban ezt az idézetet végrehajtaná, lementi a programunk teljes állapotát (ez többek között az adatverem, a hívási verem, a kivételverem és egyéb vermek teljes mentésével jár) és meghívja a kódidézetet úgy, hogy a veremtetőn egy bonyolult adatstruktúra (állapotfolytatás) lesz a sok lementett verem tartalmával. Ez a művelet úgy is felfgható, hogy lementettük a programunknak azt a jövőjét/maradékprogramját, mely akkor állna elő, ha a paraméterként megadott kódidézetet nem futtatnánk le - mint azt mondtuk, a adatverem állapota is mentésre kerül, ugyanúgy, mint minden más dolog, ami a program állapotáért felelős.
A paraméterként megadott kódidézetnek a stack effect-je a következő alakú kell legyen: ( continuation -- ). Ez azt jelenti, hogy a callcc0-tól paraméterként kapott kontinuációnak az eltűntetéséért ő felel. A legegyszerűbb esetben semmire se használja fel az állapotot (drop, vagy azonnali continue), a második legegyszerűbb esetben pedig mondjuk kiirja a pont operátorral. Ezek azonban nem a kifejezetten érdekes esetek, mert akkor lesz értelme az egésznek, ha valamilyen műveletet végzünk és bizonyos esetekben kiadunk egy continue parancsot, amivel bizonyos értelemben meg-nem-történtté tehetjük a kódidézetben foglaltakat (kivéve például az outputra történő írást!). A continue szó tehát pont egy continuitation-t fogyaszt el a stackről és csak annyit csinál, hogy visszaállítja a mentésben leírt állapotot. A mentett állapotban természetesen mint azt mondtuk nem a jelen futási ág jövője van tárolva, hanem a "mi lenne ha a kódidézetet nem kéne végrehajtani" állapot (ugyebár nyilván végtelen ciklushoz vezetne a másik megközelítés).
Néhány példa, mely az állapotfolytatások megértéséhez közelebb hozhatja az olvasót:
Látszólag az utóbbi információk fényében a kivételkezelés megvalósítása kifejezetten hatékonytalannak tűnhet factorban, de a valóságban az optimalizáló fordító miatt nem feltétlen pont így néznek ki a dolgok. A szemantika ez, de hogy mi történik ennél lejjebb, ha már kódot fordítunk az Slava Pestov asztala....