Hlavní navigace

Joy: programovací jazyk od protinožců

15. 1. 2008
Doba čtení: 14 minut

Sdílet

Druhá část článku o netradičním programovacím jazyku Joy je zaměřena především na popis datových typů, se kterými je možné v tomto jazyku pracovat. Také si ukážeme některé poněkud netradiční způsoby programování, jež jsou umožněny syntaxí i sémantikou Joye, tj. postfixovou notací a citací programů.

Obsah

1. Základní a agregační datové typy podporované v jazyce Joy
2. Zpracování základních datových typů
3. Práce se seznamy
4. Množiny
5. Znaky a řetězce
6. Citace programů a jejich následná evaluace
7. Využití citace programů v praxi
8. Odkazy na Internetu
9. Obsah závěrečné části článku

1. Základní a agregační datové typy podporované v jazyce Joy

Podobně jako v mnoha dalších programovacích jazycích, i v jazyce Joy se odlišují základní (primitivní) a agregační (kompozitní, složené či strukturované) datové typy. Jazyk Joy sice nepoužívá proměnné, dokonce ani tento termín nezavádí, ale rozlišuje a důsledně kontroluje, jaká hodnota či hodnoty jsou uloženy na zásobníku – z tohoto pohledu se tedy jedná o dynamicky typovaný jazyk, ve kterém je datový typ přímo přiřazen hodnotě uložené na zásobníku.

Mezi základní, neboli primitivní datové typy patří především celá čísla (integer), znaky (character), pravdivostní hodnoty true a false (truth values, boolean) a nakonec byla do jazyka navíc, tj. oproti původnímu návrhu, přidána i podpora pro číselné hodnoty reprezentované v systému pohyblivé řádové čárky (float).

Mezi agregační datové typy patří seznam (list), množina (set) a řetězec (string). Kupodivu není přímo v jazyce zabudován datový typ asociativní pole čili hešovací mapa, ovšem to jde poměrně jednoduše nahradit podle potřeb buď seznamem nebo množinou.

joy21

Jeden z prvních kalkulátorů s RPN od firmy HP (HP 9810)

2. Zpracování základních datových typů

Práce s celými čísly, které mohou mít znaménko a rozsah většinou od –231..231-1 (v závislosti na cílové platformě i vyšší) do značné míry reflektuje klasické RPN (Reverse Polish Notation) kalkulačky i zásobníkový programovací jazyk Forth. Podle očekávání jsou k dispozici základní aritmetické operace představované postfixovými funkcemi +, -, * a /.

Navíc je možné spočítat zbytek po dělení pomocí funkce rem, absolutní hodnotu funkcí abs, zjistit znaménko či nulovost čísla pomocí sign, otočit znaménko přes neg, použít inkrementaci či dekrementaci (succ, pred) a samozřejmě na dvojici celých čísel aplikovat i běžné relační operace typu <, <=, >, >=, = či !=. Nesmíme ovšem zapomenout na to, že se všechny vzorce zapisují v postfixové notaci:

operátor tečky zajistí tisk číselné hodnoty
uložené na vrcholu zásobníku
1 2 + .
3

všechny operace mají stejnou prioritu
2 3 4 * + .
14

obrácení pořadí aplikace operátorů dá
v tomto případě kupodivu :-) stejný výsledek
2 3 4 + * .
14

inkrementace
2 succ 3 succ + .
7

všechny početní operace lze
samozřejmě vzájemně zkombinovat
5 4 max 3 2 min rem .
1

za dělení nulou nám počítač skoro
ve všech programovacích jazycích vynadá
2 1 pred / .
run time error: non-zero divisor needed for /

vytvoření a použití funkce, která současně
vypočte zbytek po dělení i celočíselný podíl
a následně uloží oba výsledky na zásobník
DEFINE
divmod == dup swapd dupd / rotate rem
.

7 2 divmod ..
1
3
6 2 divmod ..
0
3
6 3 divmod ..
0
2
6 4 divmod ..
2
1 

Práce s pravdivostními hodnotami true a false je obdobná jako v dalších programovacích jazycích. K dispozici jsou základní logické operace jako not, and, or či xor, přičemž výsledkem těchto operací je opět pravdivostní hodnota, nikoli číslo 0 či 1, jak je tomu například v céčku. Také výsledek aplikace relačních operátorů na čísla či znaky nabývá pravdivostní hodnoty true či false:

tisk pravdivostní tabulky logické funkce not
true not .
false
false not .
true

tisk pravdivostní tabulky logické funkce and
false false and .
false
false true and .
false
true false and .
false
true true and .
true

tisk pravdivostní tabulky logické funkce or
false false or .
false
false true or .
true
true false or .
true
true true or .
true

tisk pravdivostní tabulky logické funkce xor
false false xor .
false
false true xor .
true
true false xor .
true
true true xor .
false

kombinovaný logický výraz
3 2 > 10 5 <= and .
false 

Znaky, přesněji řečeno znakové literály, jsou uvozeny prefixem ' (apostrof) a práce s nimi je v mnoha ohledech obdobná práci s číselnými hodnotami. Ke znaku je možné přičíst celé číslo (potom se patřičným způsobem změní ASCII kód znaku), znaky je možné porovnávat pomocí relačních operátorů, existují funkce pro převod znaku na ASCII kód či naopak pro převod ASCII kódu na znak (chr, ord) atd. Opět následují jednoduché ukázky:

'a .
'a
'a 10 + .
'k
'a ord .
97
64 chr .
'@
65 chr .
'A
65 32 + chr .
'a
'a 'b < .
true
'z 'b < .
false 

3. Práce se seznamy

Jedním z agregačních (někdy také nazývaných kompozitních) datových typů, se kterými programovací jazyk Joy nativně pracuje, jsou seznamy. Zde je patrná souvislost s většinou zavedených funkcionálních jazyků, ve kterých se práce se seznamy velmi často objevuje jako jedna ze základních podporovaných operací. Příkladem může být Lisp, ve kterém je seznam reprezentován speciálně zkonstruovaným řetězcem na sebe navazujících tečka-dvojic (na nejnižší úrovni tedy Lisp nepracuje se seznamy, ale právě s tečka-dvojicemi). V programovacím jazyce Joy jsou položky nacházející se v seznamech zapisované do hranatých závorek, přičemž oddělovačem je libovolný bílý znak. Seznamy mohou být i prázdné (takový seznam se potom zapisuje pouze jako dvojice hranatých závorek bez hodnot mezi těmito závorkami), či mohou naopak obsahovat další podseznamy – to je mimochodem jeden z příkladů datové rekurze.

Seznamy je možné vytvářet (konstruovat) pomocí funkce cons. V případě použití této funkce musí být na vrcholu zásobníku umístěn seznam a na druhém místě pak hodnota do seznamu přidávaná (klidně se může jednat o další seznam). Pokud je program napsán tak, že jsou položky na zásobníku prohozeny, tj. na vrcholu je uložena přidávaná hodnota (což je docela obvyklý případ), lze místo funkce cons použít funkci nazvanou swons, která je vlastně kompozicí funkcí swap a cons. Pro přístup k první položce („hlavě“, head) resp. zbytku seznamu („ocasu“, tail) se používají funkce first a rest (Lispaři v nich jistě poznali slavné car a cdr). Pokročilejší práci se seznamy si uvedeme v dalším textu, zde pouze pro ukázku uvedu několik základních operací a jejich výsledků:

získání prvního prvku seznamu (head) a jeho výpis
[1 2 3 4] first .
1

získání seznamu bez prvního prvku (tail) a jeho výpis
[1 2 3 4] rest .
[2 3 4]

nad prázdným seznamem není možné použít funkci first
[] first .
run time error: non-empty list needed for first

ani funkci rest
[] rest .
run time error: non-empty list needed for rest

ovšem funkce rest může naopak vrátit prázdný seznam
[1] rest.
[]

trošku složitější kompozice funkcí
nad seznamem obsahujícím podseznam
[1 [2 3] 4] rest first .
[2 3]

vytvoření seznamu – přidání
prvku do stávajícího seznamu
1 [2] cons .
[1 2]

vždy je nutné přidávat položky do seznamu,
i když ten může být zpočátku prázdný
(nelze spojit dva prvky, ale vždy prvek a seznam)
1 2 3 4 [] cons cons cons cons .
[1 2 3 4]

příklad použití funkce swons
[] 1 swons .
[1]

ukázka rozdílu mezi funkcemi cons a swons
[1 2] [3 4] cons .
[[1 2] 3 4]
[1 2] [3 4] swons .
[[3 4] 1 2] 

Ve standardní knihovně programovacího jazyka Joy jsou dostupné i další funkce, které je možné použít pro práci se seznamy. Jejich význam bude patrný z níže uvedených demonstračních příkladů.

Přístup k obsahu seznamu:

funkce "at" slouží k přístupu k položkám seznamů
[10 20 30 40] 0 at .
10

[10 20 30 40] 1 at .
20

číslování položek začíná od nuly,
tj. nejvyšší index je v tomto případě roven 3
[10 20 30 40] 4 at .
run time error: smaller index needed for at

záporné indexy nelze použít (na rozdíl od Pythonu)
[10 20 30 40] -1 at .
run time error: non-negative integer needed for at

funkce "of" pracuje s opačným pořadím
operandů než funkce "at"
0 [10 20 30 40] of .
10

1 [10 20 30 40] of .
20

4 [10 20 30 40] of .
run time error: smaller index needed for of 

Odstraňování položek ze seznamů:

odstranění celého seznamu ze zásobníku
[1 2 3] pop .
_

rozdělení seznamu na první prvek
a zbytek (opak konstrukce seznamu)
[1 2 3] uncons ..
[2 3]
1

opak operace "swons"
[1 2 3] unswons ..
1
[2 3]

odstranění prvních N položek ze seznamu
[1 2 3 4] 2 drop .
[3 4]
[1 2 3 4] 0 drop .
[1 2 3 4]
[1 2 3 4] 1000 drop .
[]

odstranění položek od N-tého indexu
[1 2 3 4] 2 take .
[1 2]
[1 2 3 4] 0 take .
[]
[1 2 3 4] 1000 take .
[1 2 3 4] 

Další operace se seznamy:

zjištění délky seznamu (s jeho
odstraněním ze zásobníku)
[1 2 3] size .
3

zjištění délky bez odstranění
vlastního seznamu ze zásobníku
[1 2 3] dup size ..
3
[1 2 3]

položky seznamu je možné uložit
jednu po druhé na zásobník
[1 2 3] unstack .
1
2
3

spojení dvou seznamů
(rozdílné chování oproti cons!)
[1 2 3] [4 5 6] concat .
[1 2 3 4 5 6]
[1 2 3] [4 5 6] cons .
[[1 2 3] 4 5 6] 
joy22

Kalkulátor s RPN z roku 1968 (HP 9100)

4. Množiny

Agregační datový typ množina (set) je v mnoha ohledech zajímavý, už jen z toho důvodu, že mnohé programovací jazyky tento typ ani neimplementují, což je škoda, protože by se hodil v mnoha algoritmech. Množina je vytvářena podobně jako seznam, ale místo hranatých závorek umístěných okolo hodnot se používají závorky složené, tj. znaky { a }. Množina může být – podobně jako seznam – prázdná, potom je použito prázdných závorek.

Do množin je možné ukládat jak celočíselné hodnoty, tak i znaky, které jsou však před uložením do interní reprezentace množiny převedeny na své ASCII hodnoty. V programovacím jazyce Joy je maximální velikost množiny, tj. počet prvků, shora omezen, typicky na hodnotu 32 (to odpovídá implementaci množiny v jednom 32bitovém registru procesoru). Ovšem toto omezení maximálního počtu prvků má vliv i na způsob výpočtu číselné hodnoty v případě, že se do množiny vkládají znaky – jejich ASCII hodnota je bitově maskována tak, aby výsledek ležel v rozsahu 0..31.

Nad množinami je možné provádět základní množinové operace, tj. sjednocení (funkce or), průnik (funkce and), symetrický rozdíl dvou množin (funkce xor) a výpočet doplňku množin (funkce not) vůči „jednotkové“ množině, která obsahuje všech 32 prvků. Všimněte si, že názvy množinových funkcí jsou stejné jako u funkcí pracujících s logickými hodnotami, jedná se tedy o jakousi podobu přetížených operátorů známých z jiných programovacích jazyků. Toto chování je umožněno pomocí takzvaných predikátů, jejichž zavoláním je možné zjistit, jaké datové typy mají hodnoty uložené na zásobníku.

Prvky nemusí být při vkládání do množiny seřazeny (na pořadí tedy nezáleží, jako u skutečných množin) a samozřejmě je dovoleno i vkládání duplikátů. Výsledná množina je při tisku setříděna (opět to vyplývá z interního způsobu práce s množinami) a duplikáty nejsou vypsány. Následuje několik komentovaných příkladů, na kterých je práce s množinami ozřejmena:

sjednocení dvou tříprvkových množin
{1 2 3} {3 4 5} or .
{1 2 3 4 5}

průnik dvou tříprvkových množin
{1 2 3} {3 4 5} and .
{3}

symetrický rozdíl dvou množin
(ve výsledné množině zůstanou pouze prvky,
které NEleží v obou množinách)
{1 2 3} {3 4 5} xor .
{1 2 4 5}

výsledkem některých množinových operací
může být prázdná množina
{1} {2} and .
{}

doplněk množiny vůči "jednotkové"
množině s 32 prvky
{0 1 2 3 4 5 6 7 8 9 10} not .
{11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31}

prvky množin jsou vždy tištěny setříděné
{9 8 7 6 5 4 3 2 1} .
{1 2 3 4 5 6 7 8 9}

znaky jsou před vložením do množiny
převedeny na své ASCII kódy
{'a 'b 'c 'd} .
{1 2 3 4}

převod znaků na ASCII kódy funguje
i při množinových operacích
{'a 'b 'c 'd} {'b 'c 'd 'e} and .
{2 3 4}

některé rozdílné znakové literály
a číselné hodnoty představují jako
prvek množin ve skutečnosti duplikáty
{1 'a 'A}.
{1} 

5. Znaky a řetězce

Práci se znaky jsme si popsali v první kapitole věnované práci se základními (primitivními) datovými typy. Podobně jako v dalších programovacích jazycích, i v jazyku Joy je umožněno zpracování řetězců, přičemž řetězce jsou chápány tradičně jako sekvence znaků. Zatímco jednotlivé znakové literály jsou uvozeny pouze prefixem ' (apostrof), u řetězců musí být zadány uvozovky, a to jak před jeho začátkem, tak i za jeho koncem. Je to ostatně logické, protože zatímco délka jednoho znaku je vždy konstantní a předem známá, je do řetězců nutné zapisovat i takzvané bílé znaky, tj. mezery a tabulátory. Prázdný řetězec, tj. řetězec o délce nula znaků, ze zapisuje tak, jak je to obvyklé v dalších programovacích jazycích: pomocí dvojice uvozovek. Pro práci s řetězci existuje několik zabudovaných i knihovních funkcí, z nichž některé jsou uvedeny v následujících demonstračních příkladech:

uložení řetězce na zásobník s jeho
výpisem a odstraněním
"hello Joy" .
"hello Joy"

výpis řetězce bez uvozovek pomocí
funkce putchars
"Hello world" putchars .
Hello world

spojení dvou řetězců pomocí operátoru concat
"hello " "world" concat .
"hello world"

převod čísla uloženého v řetězci na skutečnou
numerickou hodnotu se specifikací báze (soustavy)
"1234" 8 strtol .
668

pokud je číselná soustava nulová, pokusí se
funkce strtol sama zjistit základ
"1234" 0 strtol .
1234

jako základ je v tomto případě zvolena šestnáctka
"0x1234" 0 strtol .
4660
"0xffff" 0 strtol .
65535

nyní naopak osmička (céčkovská konvence)
"0100" 0 strtol .
64

převod řetězce obsahujícího desetinné číslo
na skutečnou hodnotu typu float
"52.5" strtod .
52.5 

Řetězce tvoří plnohodnotný datový typ, to znamená, že je možné ukládat řetězce do seznamů, zapisovat řetězce do souborů či je naopak ze souborů načítat apod. Naopak řetězec jako celek je možné pokládat za seznam a přistupovat k jeho jednotlivým položkám nebo podřetězcům pomocí funkcí pracujících se seznamy. V dalších příkladech jsou ukázány některé často používané operace, které se s řetězci mohou provádět:

řetězce je možné vložit do seznamu
"Hello" "world" [] cons cons .
["Hello" "world"]

načtení řetězce (jednoho řádku)
ze standardního vstupu
stdin fgets .
vypíše se zadaný vstup

získání prvního znaku v řetězci
"Hello" first
'H

získání podřetězce bez prvního znaku
"Hello" rest .
"ello"

přístup k jednotlivým znakům pomocí operátoru at
"Hello" 3 at .
'l

přístup k jednotlivým znakům pomocí operátoru of
4 "Hello" of .
'o

lexikografické porovnání dvou řetězců
"abc" "zzz" < .
true

lexikografické porovnání dvou řetězců
"abc" "ABC" < .
false 
joy23

Další kalkulačka s RPN, tentokrát vyrobená v SSSR (B3–19M)

6. Citace programů a jejich následná evaluace

Joy se podobá programovacímu jazyku Lisp či Scheme nebo i Logu tím, že striktně nerozlišuje mezi programem a daty. Stejně jako v Lispu je program považován za seznam příkazů, tj. volání funkcí či kombinátorů. To samozřejmě představuje velmi silnou zbraň tohoto jazyka, protože programy nebo jejich části lze s využitím všech funkcí a kombinátorů vytvářet nebo modifikovat (tyto operace probíhají na zásobníku, ostatně jako všechny výpočty). Zdaleka nejběžnějším idiomem je citace programů, která spočívá v tom, že je program zapsán na zásobník jako běžný seznam, tj. s využitím hranatých závorek, a posléze je tento program buď přímo spuštěn nebo použit v dalších operacích, například podmíněných výrazech, smyčkách či náhradě rekurze pomocí rekurzivních manipulátorů.

Nejjednodušší možností, jak s částí programu zapsanou v seznamu pracovat, je jeho spuštění. To lze provést pomocí operátoru i (interpret), který svým použitím odpovídá příkazu eval známého z jiných programovacích jazyků. Ukažme si jednoduchý příklad:

[1 2 + 3 * print] i
.
9 

Nejprve je zapsán blok kódu do hranatých závorek, tj. jedná se o běžný seznam, se kterým interpreter jazyka Joy nic dalšího neprovádí, pouze tento seznam uloží na vrchol zásobníku. Poté je však zavolán operátor i, který ze zásobníku vyjme seznam (pokud se tam samozřejmě nachází, jinak by se jednalo o běhovou chybu) a pokusí se tento seznam interpretovat, tj. postupně spouštět jeho jednotlivé položky. Vzhledem k tomu, že je v seznamu opravdu uložen validní programový kód (konkrétně sekvence operací push, součet, násobení a tisk hodnoty uložené na vrcholu zásobníku), je tento program interpretován a pomocí operátoru tečky se můžeme přesvědčit, že se výpočet skutečně provedl korektně.

O tom, že se s výše zapsaným programem-seznamem dá pracovat i s pomocí funkcí pro zpracování seznamu, svědčí například následující úryvek kódu, ve kterém se spočte délka seznamu, posléze se získá jeho třetí až šestý prvek a nakonec jeho prvek poslední:

na zásobník uložíme seznam stejný,
jako v předchozím příkladu
[1 2 + 3 * print]

vypíšeme délku tohoto seznamu
(duplikace zabrání odstranění seznamu
ze zásobníku, protože seznam budeme
využívat v dalších operacích)
dup size .
6

získáme třetí až šestý prvek seznamu
(opět je nutná duplikace)
rest rest dup .
[+ 3 * print]

nakonec získáme poslední prvek seznamu
rest rest rest first .
print

a přesvědčíme se, že zásobník skutečně
zůstal prázdný
.
_ 

7. Využití citace programů v praxi

Citace programů se velmi často používá v praxi. Typickým příkladem použití je náhrada podmíněného příkazu (který Joy jako zvláštní syntaktickou kategorii ani nezná) za operátor nazvaný ifte (if-then-else). Tento operátor očekává na zásobníku tři citované programy – první je spuštěn (vyhodnocen) ihned po zavolání operátoru ifte a podle výsledku vyhodnocení true/false se buď spustí druhý citovaný program, nebo naopak třetí citovaný program. Po proběhnutí operátoru ifte jsou všechny tři citované programy ze zásobníku odstraněny:

jednoduché rozvětvení na základě podmínky
[1 2 <] ["je mensi"] ["je vetsi"] ifte .
"je mensi"

nastavíme počáteční podmínky tak,
aby se podmínka negovala
[1 -2 <] ["je mensi"] ["je vetsi"] ifte .
"je vetsi"

test, zda hodnota uložená na vrcholu zásobníku
leží v rozsahu (0, 10)
5 [dup 0 > swap 10 < and] [100 *] ["chyba rozsahu"] ifte .
500

50 [dup 0 > swap 10 < and] [100 *] ["chyba rozsahu"] ifte .
"chyba rozsahu"

0 [dup 0 > swap 10 < and] [100 *] ["chyba rozsahu"] ifte .
"chyba rozsahu"

vytvoření funkce pro výpočet hyperbolického
průběhu s testem na nulový parametr
DEFINE
hyperbola == [dup 0 =] [0] [1000 swap /] ifte
.

test nově nadefinované funkce hyperbola
1 hyperbola .
1000

2 hyperbola .
500

3 hyperbola .
333

10 hyperbola .
100

nyní zkusíme zadat testovaný parametr 0
0 hyperbola .
0 

Citaci programů si ukážeme i v poslední části tohoto článku, protože i u rekurzivních kombinátorů se citace velmi často používá, ať už pro zadání výrazu volaného pro test, zda se má rekurze ukončit, či pro zápis vlastního rekurzivně volaného těla funkce. Příkladem jednoduchého kombinátoru může být rekurzivní kombinátor while, který svým použitím simuluje klasickou smyčku typu while známou z imperativních programovacích jazyků:

    podmínka  tělo funkce    volání kombinátoru
10   [0 >]   [dup put 1 -]   while .
10 9 8 7 6 5 4 3 2 1 0
(poslední hodnota není vypsána smyčkou, ale operátorem .)

jednodušší způsob využívající operátoru pred
10   [0 >]   [dup put pred]  while .
10 9 8 7 6 5 4 3 2 1 0
(poslední hodnota není vypsána smyčkou, ale operátorem .) 
joy24

V SSSR se vyrábělo několik typů RPN kalkulaček (B3–21)
(zajímavé je, že některé klávesy jsou psané latinkou a další azbukou)

8. Odkazy na Internetu

joy25

I zde je patrná inspirace designem kalkulaček HP (MK-52, podobná HP-42)

CS24_early

9. Obsah závěrečné části článku

Ústředním tématem třetí a současně i poslední části článku o netradičním programovacím jazyce Joy bude podrobnější vysvětlení rekurzivních kombinátorů. Teoretická část bude samozřejmě doplněna i mnoha demonstračními příklady, včetně nerekurzivních variant původně rekurzivně napsaných algoritmů.

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.