A Factor programozási nyelv

Utasítások, vezérlési szerkezetek

Szavak, Verem manipulációs szavak

Factorban a literálok a veremre kerülnek, a szavak pedig szekvenciálisan futva a vermen dolgoznak és azon keresztül kommunikálnak más szavakkal is. Azt már láthattuk a korábbi fejezetekben, hogyan is működik ez alapvetően, de ott nem foglalkoztunk azzal, hogy mi van, ha valami már a veremre kerül és nekünk rossz helyen van, mert mondjuk más sorrendben lenne szükségünk a veremtartalomra, vagy bizonyos felesleges elemeket el kéne tűntetnünk a veremről. Az itt vázolt problémák megoldását a verem-manipulációs szavak biztosítják számunkra.

A verem manipulációs szavak(mögé írtuk a stack effektet):

USE: kernel drop ( x -- ) ! Eldobja a verem tetején álló elemet 2drop ( x y -- ) ! Eldobja a verem tetején álló 2 elemeket 3drop ( x y z -- ) ! Eldobja a verem tetején álló 3 elemeket nip ( x y -- y ) ! Ráteszi a verem legfelső elemét a legfelső ! alattira 2nip ( x y z -- z ) ! Az előzőhöz analóg, csak több elemmel dup ( x -- x x ) ! Megduplázza a veremtetőt 2dup ( x y -- x y x y) ! Megduplázza a verem legfelső elemeit 3dup ( x y z -- x y z x y z) ! Értelemszerűen működik over ( x y -- x y x ) ! Átemel egy elemet a verem tetején 2over ( x y z -- x y z x y ) ! Átemel két elemet a verem tetején ! (utóbbiaknál az eredeti helyen is megmarad) pick ( x y z -- x y z x ) ! Átemelés két elemen keresztül swap ( x y -- y x ) ! Felcseréli a verem két legfelső elemét dupd ( x y -- x x y ) swapd ( x y z -- y x z ) ! Ezek a legfelső elemet békénhagyják(d~deep) rot ( x y z -- y z x ) ! Verem rotációja (balra) -rot ( x y z -- z x y ) ! Verem rotációja (jobbra)

Minden szóhoz meg kell adni egy stack effektet, amikor létrehozzuk(használatukkor viszont nem szükséges), mely egy nagyon alacsonyszintű típusellenőrzést és egyben szükséges minimális dokumentációt nyújt a külvilág felé. Előbbi azzal támogatott, hogy a Factor-fordító ellenőrzi egy egyszerű szimulációval a programban részt vevő szavak stack-effect-jeinek egyeztethetőségét, az utóbbi pedig abból a tényből következik, hogy szavak definiálásakor ennek megadása kötelező, a fejlesztőkörnyezet pedig a bal alsó sarokban ki is jelzi ezt bármilyen beírt ismert szóra. A stack effectet úgy kell kiolvasni, hogy a két kötőjeltől balra a régi, tőle jobbra az új veremállapot látható, mely állapotokban a verem alja balrább, a teteje pedig jobbra van, vagyis ( x y z -- x ) azt jelenti, hogy x a legmélyebben van a veremben, ezen van y, azon pedig z, amit intuitíve úgy is kifejezhetnénk, hogy a stack effekt úgy olvasandó balról jobbra, amilyen sorrendben a balról jobbra látott elemek a veremre kerültek (ha előbb x-et, majd y-t, majd z-t eredményező szavakat hajtjuk végre, pont ehhez az állapothoz juthatunk).

Alacsonyszintű vezérlési mechanizmusok

A nyelvbe épített meglévő szavakkal és a fenti verem-manipulátorokkal már nagyon sok mindent meg tudunk oldani, de a bonyolultabb programokhoz valamilyen vezérlési mechanizmusokra is szükségünk van. A Factorban a többi nyelvvel(pl. Forth) ellentétben az if-hez hasonló vezérlési szerkezetek nem beépített nyelvi elemek, hanem maguk is alacsonyabbszintű építőkockákat használnak fel, először egy kicsit ezekkel foglalkozunk.

A ? feltételes kiértékelő

A nyelvben létezik egy '? ( ? true false -- true/false )' szó, mely attól függően redukálja a verem felső három elemét a legfelsőre, vagy a másodikra, hogy a harmadik legfelső elem f-e vagy sem. Ezen szó felhasználásával tehát feltételtől függően választhatunk a vermen lévő értékek között, azonban vezérlésátadásról nem beszélhetünk, ez csupán egy feltételtől függő veremmanipuláció.
A ? szó működését az alábbi példákon könnyen megérthetjük:

t "tval" "fval" ? . ! Kiírás eredménye: "tval" f "tval" "fval" ? . ! Kiírás eredménye: "fval" 42 "tval" "fval" ? . ! Kiírás eredménye: "tval", mert 42 != f 1 3 < 1 3 ? . ! Kiírás eredménye: 1, mert 1 < 3 x 0 < 0 x - x ? . -5 ! -5 a veremre dup 0 < over 0 over - swap ? swap drop . ! Eredmény: 5 42 ! 42 a veremre dup 0 < over 0 over - swap ? swap drop . ! Eredmény: 42 (a fenti sor az abszolútértékszámítás)

Kódidézetek

Motiváció:

Factorban a vezérlési szerkezeteket úgy oldják meg, hogy a programkódot is adatként kezelik, amit a stack-re lehet helyezni és később meghívni. Ennek egy machanizmusnak egy egyszerűbb változata az, amikor a backslash operátort használjunk, ami az őt követő szó kiértékelését elhalasztja és a veremre dobja azt. Ezt már használtuk például f objektum és f osztály közötti különbségtételnél, ahol azért volt rá szükségünk, hogy az f osztály, mint szó ne "fusson le" és adja vissza nekünk a singleton f objektumot. Bizonyos feltételeknek eleget tévő szavakat ezután az execute szóval futtathatunk a veremről. Például '3 2 \ + execute .' a szokásos eredményt adja, de '\ f execute' nem ugyanaz, mint f beírása. A backslash operátor segítségével már vezérlést is el lehet képzelni, ám a sok megkötés miatt nem alkalmazandó, helyette vezették be a kódidézeteket, melyek hasonló, ám sokkal magasabb szintű nyelvi elemek, gyakorlatilag névtelen függvényekhez hasonlatos kifejezések, melyek ugyanúgy stack-alapúak, de adatként kezelhetőek.

Használat, tulajdonságok:

A kódidézetekkel (idézett kód, quotation) már több korábbi fejezetben találkoztunk, például a literáloknál és a szekvencia típusoknál, mindazonáltal ott csak a szintaktikai megfontolások miatt szerepelt, illetve azért, mert megvalósítja a szekvencia protokollt. Most nézzük meg őket kicsit pontosabban.

Az kódidézetek használatát is példákkal szemléltetjük:

USE: fry { 1 2 3 } [ 2 * ] map . { -1 2 5 -3 10 -7 } [ 0 >= ] filter . ! Két hasznos alkalmazás az előző fejezethez ! kapcsolódóan (ott nem szóltunk róla, hogy ! hogyan megy a map és hasonlók gyakorlati használata -42 dup 0 < [ -1 * ] [ ] ? call . 42 dup 0 < [ -1 * ] [ ] ? call . ! Eredmény: mindkettőnél 42 ! (abszolútérték másképp történő megvalósítása) 5 '[ _ 2 * ] call . ! Eredmény: 10 (fried - azaz lyukas - qoutation [ * ] '[ 5 2 _ call ] call . ! Eredmény: 10 (ilyet is szabad csinálni!)

Először két gyakorlatilag is fontos példát láthatunk (a kódidézetek szükségesek a factorban a map és hasonló szavak gyakorlati felhasználásához), majd ízelítőként példát mutattunk arra, hogyan is használható az ebben a szekcióban ismertetett két különböző dolog együtt, egy kezdetleges vezérlés implementálására, legvégül két rövid példát adtunk a lyukas kódidézetek használatára.
Újból megvalósítottuk tehát az abszolútértéket, de most a következőképp:

A fenti kód tehát egy kezdetleges if-nek felel meg. Igazából a nyelvben lévő if pont ilyen módon van definiálva és csak egy szintaktikus cukorka, hogy ne kelljen ilyen bonyolultan írnunk elágazásokat.

Elágazás

Az előző szekció végén adtunk egy módszert programunk futásának feltételtől függő elágaztatására, de a nyelvben van erre magasabb szintű lehetőségek is. Ezek az if, case és cond szavakkal megvalósított vezérlési szerkezetek.

Az if

A legegyszerűbb elágazás a factorban. A verem tetején egy boolean értéknek és két kódidézetnek kell szerepelnie. Szemléletesen a következő a stack effect: ( a ? truequot falsequot -- b ), ha az kódidézetek valami típusú elemből b-t állítanak elő. Megjegyezzük, hogy a valóságban az ifnek bonyolultabb a stack effect deklarációja, ezzel példaként majd a következő fejezetben, az alprogramoknál foglalkozunk!
Most lássunk néhány példát, először a szokásos abszolútértéket:

-42 dup 0 < [ -1 * ] [ ] if . ! Eredmény: 42 (abszulútérték) 4242 42 2dup < [ drop ] [ swap drop ] if . ! Eredmény: 42 (min(a,b)) ! DE: ez ágyúval verébre ! 42 4242 2dup > ? -rot . ! Eredmény: 42 (szintén min(a,b)) ! Ez ilyenkor értelmesebb...

A case

Factorban lehetőségünk van bonyolult egymásba ágyazott if-eket case szerkezetté alakítani. Használatkor a stack-en az objektum kell legyen, amin switch-elni akarunk és egy asszociatív típus(legegyszerűbb esetben tömbbel megvalósítva), amiben a kulcs-érték párok kulcsmezője a switch-elt objektum lehetséges várt értékei, az értékmezője pedig a végrehajtandó akciót reprezentáló kódidézet.
Példák:

SYMBOL: yes SYMBOL: no SYMBOL: maybe SYMBOL: invalidsymbol ! Egyszerű szimbólumokat használunk, de mást is lehetne( = kell rá!) maybe ! Most jobb híján ő játtsza el a swith-objektumot a stack-en. { { yes [ ] } ! nem csinálna ekkor semmit { no [ "No way!" throw ] } ! "No way" exception dobása ebben az esetben { maybe [ "Make up your mind!" print ] } ! sima print [ "Invalid input; try again." print ] ! minden más eset } case ! Eredmény: Make up your mind!

A fenti példához lényeges megjegyezni, hogy az invalid input esetén a fenti kód nem tűnteti el a stackről az ott lévő rossz inputot!

A cond

A Factor nyelvben létezik egy szofisztikáltabb megoldás is a többágú elágazásra, mégpedig a cond szóval definiált vezérlési szerkezet, ami a case felokosításának is tekinthető. A cond ugyanúgy egy elágaztató objektumot és egy asszociatív típust vár, de az asszociatív típusnak itt már nem csak az érték, hanem a kulcsmezője is egy kódidézet, amely f vagy t értékkel kell visszatérjen a verem el nem fogyasztásával, ilyen módon nem csak értékeket írhatunk az egyes ágakhoz, hanem gyakorlatilag feltételeket kiértékelő kódidézeteket is.
Példák:

42 { { [ dup 0 > ] [ "positive" ] } { [ dup 0 < ] [ "negative" ] } [ "zero" ] } cond . drop ! Eredmény: "positive" -42 { { [ dup 0 < ] [ -1 * ] } [ ] ! Ez a sor nélkül exception-t kapunk minden nemnegatívra! } cond . ! Eredmény: 42 (yet another abszolútérték megvalósítás...)

Ciklus

Mivel Factor szavak definiálásánál mint azt majd látni fogjuk alkalmazhatunk rekurziót is, elvileg azzal és az eddigiekkel, már mindent megvalósíthatunk, de mivel nem minden probléma fejezhető ki kényelmesen rekurzióval, ciklus vezérlési szerkezetünk is van. Négy különbőző típusú ciklus áll rendelkezésünkre a times, a while, az until és a loop, ezen felül pedig egy do módosító szó is létezik, mely a while futását befolyásolja. A fentieken kívül használhatjuk az each szekvenciakombinátort is számlálós ciklusokhoz, ugyanis a times-t használva nem tudjuk elérni a ciklusszámlálót, ha olyat akarunk csinálni, ami ezt megköveteli, akkor each+iota kombót kell használnunk. A szekció végén ezt a lehetőséget is megmutatjuk.

A times

Ez a legegyszerűbb iterációs forma a nyelvben, a vermen meg kell adnunk egy számot és egy kódidézetet a times pedig szám-szor végrehajtja az idézet tartalmát.
Példák:

10 [ "A Factor király" print ] times ! Eredmény: 10sor "A Factor a király" 3 [ 0 ] times ! A stackre három nulla kerül 42 10 [ 1 + ] times . ! Eredmény: 52

A times hasznos például akkor, ha egy bonyolult kifejezés eredményét többször akarjuk a stackre tenni (sok dup felesleges helyettesíthető vele), vagy valamit szimplán csak többször kell megcsinálnunk, de a lehetőségei elég korlátozottak.

A while

Hagyományos előtesztelő ciklus, a veremre teszünk egy kódidézetet, ami változtathat a felette lévő veremtartalmon is, de rá kell tegyen arra egy igaz-hamis értéket, aztán ha a feltétel igaz, akkor másodiknak a veremre tett kódidézet fut le és ez a folyamat ismétlődik cikluikusan.
Példák:

10 [ dup 0 = not ] [ 1 - dup . ] while drop ! 9..0-ig kiírja a számokat 11 [ 1 - dup 0 = not ] [ dup . ] while drop ! 10..1-ig kiírja a számokat ! (A feltétel-idézetben változik a ciklusszámláló) 0 [ 1 - dup 0 = not ] [ dup . ] while drop ! Végtelen ciklus :-) 0 [ 1 - dup 0 >= ] [ dup . ] while drop ! Biztonságosabb megoldás, ez egyszer se fut le 0 [ 1 - dup 0 >= ] [ dup . ] do while drop ! A do miatt egyszer mindenképpen le fog futni ! 42 [ f ] [ . ] do while ! A do miatt még ez is lefut egyszer !

Az until

Szintén előtesztelő ciklus, de itt addig iterálunk, míg a feltételidézet igazat nem ad.
Példák:

42 [ t ] [ . ] until ! Semmi hatása, 42 a vermen marad ! drop 42 [ 0 = not ] [ . ] until ! Semmi hatása(a verem most üres lett a végére) 10 [ dup 0 = ] [ 1 - dup . ] until drop ! Eredmény: 9..0-ig a számok 42 [ t ] [ . ] do until ! Do miatt végrehajtódik egyszer mindenképp ! 42 [ 42 ] [ . ] until drop ! Ennek semmi hatása (42 ~= true, ui. 42 != false)

A loop

A Factor nyelvben a loop ciklus az előzőekkel ellentétben nem két kódidézettel dolgozik, hanem csak eggyel, amit addig futtat egymás után, míg a kódidézet f-et nem ad vissza!
Példák:

26 -1 [ 1 + dup dup * pick <= ] loop 1 - . drop ! Eredmény: 5 = (int)sqrt(x) ! (naív implementáció)

Az each, a range-ek és az iota

Az each eredetileg egy szekvenciák felett definiált szó, mellyel azonban egy kis ügyeskedéssel szép számlálós ciklusok írhatóak az iota segédszó segítségével, erre mutatunk itt most néhány példát:

{ 1 2 3 } [ "A szám: " write .] each ! "A szám: 1"..."A szám: 3" kiírása 3 iota [ . ] each ! 0 1 2 kiírása(3 iota szekvenciát csinál 0..2-vel -42 42 [a,b] [ . ] each ! -42..42-ig kiírja az elemeket !