Hlavní navigace

Programovací jazyk APL: programování bez smyček

29. 12. 2009
Doba čtení: 11 minut

Sdílet

V dnešní části seriálu o historii výpočetní techniky navážeme na část předchozí, v níž jsme si řekli základní informace o netradičním „hieroglyfickém“ programovacím jazyku APL. Dnes budou popsány základní funkce a operátory a taktéž si ukážeme způsob vytváření uživatelských funkcí.

Obsah

1. Práce s vektory a maticemi

2. Monadická a dyadická funkce ρ

3. Přístup k prvkům polí (použití indexů)

4. Operátory

5. Operátor „reduce“

6. Operátor „scan“

7. Uživatelské funkce

8. Funkce s parametry, návratová hodnota funkcí

9. Obsah závěrečné části článku o programovacím jazyce APL

1. Práce s vektory a maticemi

V předchozí části seriálu o historii počítačů jsme si řekli základní informace o netradičním programovacím jazyku nazvaném APL (A Programming Language), jehož autorem je Kenneth Iverson. Vysvětlili jsme si význam některých základních (neboli primitivních) funkcí a způsob jejich použití v jednoduchých výrazech. Primitivní funkce, které mohou mít buď jeden parametr (monadické funkce) nebo dvojici parametrů (dyadické funkce), se v jazyku APL zapisují formou speciálních symbolů z nichž většinu není možné reprezentovat pomocí znaků z ASCII tabulky, což je jeden z důvodů, proč programy zapisované v jazyku APL vypadají zcela jinak než programy vytvářené v mainstreamových programovacích jazycích. Ovšem zápis primitivních funkcí pomocí symbolů není ten pravý důvod, proč má jazyk APL své stálé obdivovatele. Skutečný důvod spočívá v podpoře vícedimenzionálních polí, s nimiž je možné v mnoha případech pracovat bez nutnosti použití programových smyček. Příklady si ukážeme v navazujících kapitolách.

2. Monadická a dyadická funkce ρ

Většina starších i současných překladačů a interpretrů jazyka APL podporuje tvorbu polí s maximálně 63 dimenzemi, ovšem v praxi se většinou můžeme setkat s 0-dimenzionálními poli (skalárními hodnotami, tj. čísly či znaky), jednodimenzio­nálními poli (vektory) a dvoudimenzionálními poli (maticemi). Při práci s poli hraje velkou roli monadická a dyadická funkce ρ (, reshape) a taktéž monadická funkce ι (jóta, index, též čítač). Monadická forma funkce ρ slouží ke zjištění prvků vektoru či velikosti matice (popř. víceroz­měrného pole). Výsledkem této funkce je tedy vektor obsahující tolik prvků, kolik jich odpovídá dimenzi pole a hodnota každého prvku ve výsledku odpovídá velikosti pole v dané dimenzi (v případě vektoru se tedy vrátí jediné číslo reprezentující jeho délku, u matice dvojice číslic odpovídajících počtu řádků a sloupců, u trojrozměrných polí trojice číslic atd.).

Dyadická forma funkce ρ má i přes použití stejného symbolu poněkud odlišnou úlohu – slouží k vytvoření (konstrukci) pole s danými rozměry (jedná se o první parametr zapisovaný před symbol této primitivní funkce), přičemž ve druhém parametru (zapisovaném za symbol funkce) jsou uvedeny prvky, ze kterých se pole vytvoří. Může se jednat buď o vektor nebo o jiné pole, které je „přeskládáno“ tak, aby jeho rozměry odpovídaly zadanému prvnímu parametru (právě z této vlastnosti funkce ρ je odvozen její anglický název reshape neboli změna tvaru). V některých výrazech vypsaných níže je použita i monadická funkce ι, která slouží k vytvoření (konstrukci) posloupnosti číselných hodnot, jejichž počet odpovídá hodnotě parametru této funkce. Ve všech dnešních příkladech bude text zapisovaný programátorem odsazen o čtyři znaky doprava, zatímco text vypisovaný překladačem bude pro odlišení začínat na prvním sloupci:

    Vektor1 ← 1 2 3 4 5 6
    Vektor1
1 2 3 4 5 6
    ρVektor1
6

    Vektor2 ← ι10
    Vektor2
1 2 3 4 5 6 7 8 9 10
    ρVektor2
10

    Vektor3 ← 10 ρ 0
    Vektor3
0 0 0 0 0 0 0 0 0 0
    ρVektor3
10

    Matice1 ← 4 3 ρ 1 2 3 4 5 6 7 8 9 10 11 12
    Matice1
1 2 3
4 5 6
7 8 9
10 11 12
    ρMatice1
4 3

    Matice2 ← 3 3 ρ ι 9
    Matice2
1 2 3
4 5 6
7 8 9
    ρMatice2
3 3

    Matice3 ← 2 4 ρ 0
    Matice3
0 0 0 0
0 0 0 0
    ρMatice3
2 4

    Matice4 ← 2 4 ρ Matice2
    Matice4
1 2 3 4
5 6 7 8
    ρMatice4
2 4

    Matice5 ← 5 2 ρ Vektor2
    Matice5
1 2
3 4
5 6
7 8
9 10
    ρMatice5
5 2

3. Přístup k prvkům polí (použití indexů)

V programovacím jazyku APL je možné, podobně jako v prakticky všech moderních programovacích jazycích, přistupovat k jednotlivým prvkům polí, tj. k prvkům vektorů, matic atd. Na následujícím příkladu je ukázán způsob vytvoření jednorozměrného pole (vektoru) se sedmi prvky a přístup ke třetímu prvku tohoto pole pomocí indexu zapsaného do hranatých závorek. Posléze je vytvořeno dvourozměrné pole (tj. matice) velikosti 3×3 prvky a následně je proveden přístup k prvku ležícímu uprostřed této matice. Dvojice indexů je v tomto případě taktéž zapsána do závorek, přičemž indexy jsou od sebe odděleny středníkem (nikoli čárkou, ta má zcela jiný význam, protože slouží ke spojování polí!) Povšimněte si, že prvky pole jsou číslovány od jedničky (na rozdíl od jazyků odvozených od céčka), což zjednodušuje poměrně velkou část algoritmů, především v těch případech, kdy se nepoužívají programové smyčky, kterým se v APL můžeme v mnoha případech velmi elegantně vyhnout:

    Vektor ← 11 22 33 44 55 66 77
    Vektor
11 22 33 44 55 66 77
    Vektor[3]
33

    Matice ← 3 3 ρ ι 9
    Matice
1 2 3
4 5 6
7 8 9
    Matice[2;2]
5

Výše uvedená dvojice příkladů pravděpodobně žádného vývojáře nepřekvapila, protože přístup k jednomu prvku pole je v naprosté většině současných programovacích jazyků velmi podobný, samozřejmě pokud zanedbáme některé méně podstatné implementační detaily (způsob zápisu závorek, hodnota indexu prvního prvku, způsob oddělení indexů). Zajímavější je však to, že v programovacím jazyku APL je možné vybrat libovolný počet prvků pole, přičemž indexy prvků ve výběru se mohou opakovat nebo být různě zpřeházené. Výsledkem všech následujících operací je vektor, tj. nikoli pouze skalární hodnota. U posledního příkazu je navíc použita monadická varianta funkce ι, která v generuje indexy od jedničky až do zadané hodnoty (v tomto konkrétním případě se jedná o čísla 1 2 3 4 5, takže se vrátí hodnoty prvního až pátého prvku):

    Pole ← 11 22 33 44 55 66 77
    Pole
11 22 33 44 55 66 77
    Pole[1 2 3]
11 22 33
    Pole[3 2 1]
33 22 11
    Pole[1 3 5 5 3]
11 33 55 55 33
    Pole[ι5]
11 22 33 44 55

Ovšem všechny výše uvedené příklady jsou jen pouhým náznakem síly operací, které je možné s prvky polí provádět. Prvkům polí lze přiřazovat hodnoty (to samozřejmě nikoho neohromí), ovšem mnohem zajímavější je, že hodnoty lze přiřadit i více prvkům polí současně (v podstatě se jedná o opak výše ukázaných operací vrácení hodnot vybraných prvků polí). V některých případech je vhodné pracovat přímo s celým sloupcem či řádkem dvourozměrné i vícerozměrné matice. I tuto operaci programovací jazyk APL samozřejmě podporuje. Nejlépe se tato operace vysvětluje na dvourozměrných maticích: pokud je zapotřebí vybrat celý řádek matice, postačuje do hranatých závorek zadat číslo řádku, za nímž následuje pouze středník (bez dalších hodnot). Výběr sloupce je obdobný – do hranatých závorek se zapíše středník následovaný indexem sloupce (ve skutečnosti jsou hodnoty uložené ve sloupci převedeny na řádkový vektor, což je však v kontextu jazyka APL logické). Dokonce je možné jednotlivé způsoby zkombinovat, tj. například vybrat pouze několik hodnot na jednom řádku matice atd. – viz následující příklady:

    Pole ← 11 22 33 44 55 66 77
    Pole
11 22 33 44 55 66 77
    Pole[7] ← 777
    Pole
11 22 33 44 55 66 777
    Pole[1 2 3] ← 0 42 6502
    Pole
0 42 6502 44 55 66 777
    Pole[7 6] ← 0 1
    Pole
0 42 6502 44 55 1 0
    Pole[ι5]
0 42 6502 44 55
    10×Pole[ι5]
0 420 65020 440 550

    Matice ← 3 3 ρ ι 9
    Matice
1 2 3
4 5 6
7 8 9
    Matice[2;2]
5
    Matice[1;]
1 2 3
    Matice[;1]
1 4 7
    Matice[;1] + Matice[;3]
4 10 16
    2×Matice[1;]
2 4 6
    Matice[3;1 3]
7 9
    Matice[;3] ← Matice[;1] + Matice[;2]
    Matice[3;] ← Matice[2;] × Matice[3;]

4. Operátory

Dalším zajímavým a přitom velmi důležitým nástrojem vývojářů v programovacím jazyce APL jsou takzvané operátory, pomocí nichž je možné například aplikovat nějakou funkci na všechny prvky pole, zkombinovat vzájemně všechny prvky v poli (z vektoru o n prvcích se aplikací operátoru inner product stane matice o rozměrech n×n prvků) atd. Právě díky operátorům nemusí programátoři tvořící aplikace v jazyku APL vytvářet programové smyčky, protože například součet (sumu) či součin (produkt) všech prvků vektoru lze velmi jednoduše zapsat pomocí dyadické funkce + nebo × aplikované postupně na všechny prvky vstupního vektoru. Programovací jazyk APL obsahuje šest základních operátorů, ovšem některé jeho modernější implementace nabízí operátorů více (většinou osm). Šestice základních operátorů, se kterou se můžeme setkat ve většině dialektů jazyka APL, je vypsána v následující tabulce:

Operátor Unicode Jméno Poznámka
¨ 00a8 each
/ 002f slash, reduce kapitola 5
\ 005c backslash, scan kapitola 6
[] 005b, 005d axis kapitola 3
. 002e inner product bude popsán příště
◦. 25e6, 002e outer product bude popsán příště

5. Operátor „reduce“

Základním operátorem, využívaným v prakticky jakékoli aplikaci napsané v programovacím jazyku APL, je operátor „reduce“ nazývaný také „slash“ (druhý název tohoto operátoru vznikl ze symbolu, kterým je operátor zapisovaný – /). Tento operátor se zapisuje za symbol dyadické funkce a má ten význam, že vybranou funkci postupně aplikuje (volá) na všechny prvky pole. Prvním argumentem každé aplikace funkce je mezivýsledek (většinou skalární hodnota, prvním mezivýsledkem je první prvek pole), druhým argumentem pak n-tý prvek pole. Operátor reduce tedy předané pole zpracovává postupně, prvek po prvku. Typickým příkladem může být výpočet sumy (součtu, v matematice zapisovaný symbolem Σ) všech prvků vektoru či součinu všech jeho prvků (v matematice se používá symbol Π). Tyto dvě často používané operace je možné s využitím operátoru reduce naprogramovat v jazyku APL velmi snadno bez použití programové smyčky a pomocných proměnných:

    +/ 1 2 3 4
10
    ×/ 1 2 3 4
24

Při použití operátoru reduce samozřejmě můžeme použít jakoukoli dyadickou funkci, například i minule popsané funkce ᒥ a ᒪ, které vrací větší popř. menší z obou předaných argumentů. Pokud se tyto funkce postupně aplikují na celý vektor, vrátí největší nebo nejmenší prvek tohoto vektoru:

    ᒥ/ 75 72 78 90 69 77 81 88
90

    ᒪ/ 75 72 78 90 69 77 81 88
69

Výpočet průměru řady čísel uložených ve vektoru je velmi jednoduchý když si uvědomíme, že počet čísel, tj. délku vektoru, lze zjistit pomocí funkce ρ. Nejprve se tedy všechna čísla sečtou a následně se mezivýsledek vydělí jejich počtem:

    X ← 1 2 3 4 5
    (+/X)÷ρX
3

Nyní si ukažme, jakým způsobem se používá operátor reduce při práci s maticemi. Při popisu tohoto operátoru jsme si řekli, že se postupně volá nějaká vybraná funkce na všechny prvky pole. V případě vektoru byla situace jednoduchá, protože prvkem vektoru byly přímo skalární hodnoty (čísla). Pokud se však operátor reduce použije na dvourozměrné matice (podobně i na vícedimenzionální struktury), jsou v tomto případě prvkem pole myšlené celé vektory (řádky matice), na jejichž prvky je funkce aplikována (jinými slovy – operátor je uplatňován na nejnižší dimenzi, tj. sloupcích):

    Matice ← 3 3 ρ ι 9
    Matice
1 2 3
4 5 6
7 8 9

    +/ Matice
6 15 24

Součet všech prvků matice lze zajistit dvojím použitím operátoru reduce. Jeho první aplikace zajistí součet všech hodnot na jednotlivých řádcích matice, druhá aplikace operátoru sečte vektor součtů (6 15 24) jednotlivých řádků:

    +/ +/ Matice
45

Sumu lze samozřejmě spočítat i pro vybraný sloupec či řádek matice, dokonce je možné specifikovat dimenzi, ve které bude operátor uplatňován (v případě dvourozměrných matic tak lze operátor aplikovat buď na sloupce nebo na řádky bez nutnosti transpozice matice):

    +/ Matice[;1]
12

    +/[1] Matice
12 15 18

6. Operátor „scan“

Dalším operátorem, se kterým se v tomto článku krátce seznámíme, je operátor nazvaný scan nebo také backslash. Tento operátor je v programech zapisovaných v jazyku APL reprezentován, jak již ostatně jeho druhé jméno napovídá, obráceným lomítkem – \. Operátor scan má chování podobné výše popsanému operátoru reduce, ovšem s jedním podstatným rozdílem – zatímco v případě použití operátoru reduce se dozvíme pouze celkový výsledek aplikace vybrané funkce na pole, je u operátoru scan vrácen vektor všech mezivýsledků, čehož je možné v některých případech využít a opět tak eliminovat potřebu tvorby programových smyček. Tento operátor lze použít spolu s jakoukoli dyadickou funkcí. Na následujících příkladech si povšimněte rozdílu v chování obou operátorů:

    +/ 1 2 3 4 5
15

    +\ 1 2 3 4 5
1 3 6 10 15

    ×/ ι4
24

    × \ ι4
1 2 6 24

7. Uživatelské funkce

V programovacím jazyku APL je možné, podobně jako v mnoha dalších programovacích jazycích, vytvářet i uživatelské funkce a rozšiřovat tak repertoár základních (primitivních) funkcí. Zatímco primitivní funkce jsou představované speciálním symbolem (ρ, ι, +, ×, ÷, ᒥ, ᒪ atd.), uživatelské funkce se pojmenovávají stejným způsobem jako proměnné, což znamená, že se v jejich jménech mohou vyskytovat běžné alfanumerické znaky (písmena a číslice, číslice se nesmí vyskytovat na začátku jména funkce), podtržítko a některé speciální znaky, jakými jsou Δ (delta) a Δ (underlined delta). Pokud tedy programátorům nevyhovuje zápis primitivních funkcí pomocí speciálních symbolů, mohou si vytvořit vlastní funkce, které vhodným způsobem „obalí“ funkce primitivní. Uživatelské funkce mohou být, podobně jako funkce primitivní, niladické (bez parametrů), monadické (s jedním parametrem) a dyadické (se dvěma parametry).

Ve vývojovém prostředí jazyka APL většinou existuje speciální editor určený pro editaci funkcí, který je možné zavolat například příkazem )EDIT, za nímž následuje jméno funkce (příkaz „)EDIT“ skutečně začíná uzavírací kulatou závorkou). Původní editor funkcí byl pouze řádkový, v modernějších implementacích jazyka APL se většinou jedná o plnohodnotný celoobrazovkový editor. Po vyvolání editoru se většinou automaticky vytvoří první řádek s názvem funkce, před nímž je uveden symbol ∇. Celá editace končí zápisem řádku s jediným symbolem ∇ (tentokrát již uvedeným bez jména funkce). V následujícím příkladu je ukázána jednoduchá niladická (bezparametrická) funkce, která nevrací žádnou hodnotu, tj. v jiných jazycích by se jednalo o proceduru. Funkce si po svém zavolání vyžádá od uživatele zadání seznamu čísel a následně spočítá a vytiskne jejich součet, průměr a největší i nejmenší hodnotu:

    ∇SIMPLE_STAT
    'Zadej radu cisel'
    SEZNAM ← ⎕
    'Bylo zadano' (ρSEZNAM) 'cisel'
    'Suma = '(+/SEZNAM)
    'Prumer = '((+/SEZNAM)÷ρSEZNAM)
    'Nejvetsi hodnota = '(ᒥ/SEZNAM)
    'Nejmensi hodnota = '(ᒪ/SEZNAM)
    ∇

8. Funkce s parametry, návratová hodnota funkcí

Skutečný programátor používající jazyk APL by výše uvedenou funkci SIMPLE_STAT nenapsal v přesně té podobě, jak jsme si ji ukázali v předchozí kapitole. Důvodem je především to, že funkce obsahuje poměrně velké množství programového kódu, který by bylo možné využít i v jiných částech aplikace a taktéž to, že funkce je použitelná pouze při ručním vstupu dat z konzole (viz řádek SEZNAM ← ⎕). Programátoři většinou rozkládají funkce na jednodušší části, které lze snadněji otestovat a vícenásobně použít. Prvním přiblížením dekompozice funkce SIMPLE_STAT by například bylo vytvoření monadické (jednoparametrické) uživatelské funkce AVERAGE, která vypočte průměr ze zadaného seznamu (vektoru) čísel a výsledek posléze předá jako svou návratovou hodnotu. Povšimněte si, jakým způsobem je při definici funkce již na prvním řádku naznačeno, že se jedná o funkci s jedním parametrem (SEZNAM) a pojmenováním speciální lokální proměnné představující návratovou hodnotu funkce (R):

    ∇R ← AVERAGE SEZNAM
    R ← (+/SEZNAM)÷ρSEZNAM
    ∇

Nově vytvořenou funkci AVERAGE lze používat stejným způsobem jako primitivní monadické funkce:

UX DAy - tip 2

    AVERAGE 1 2 3 4 5 6
3.5

    NUMBERS ← 10 20 30 40 50
    RESULT ← AVERAGE NUMBERS
    RESULT
30
    RESULT × 2
60
    2 × AVERAGE NUMBERS
60

9. Obsah závěrečné části článku o programovacím jazyce APL

Popis programovacího jazyka APL dokončíme až v následující části seriálu o historii výpočetní techniky. Ukážeme si způsob vytváření vnořených polí (hierarchických datových struktur), použití relačních funkcí spolu s funkcemi pro „kompresi“ a „dekompresi“ polí (jedná se o velmi silnou programovací techniku) a taktéž potěšíme znalce Lispu popisem funkcí take a drop (↑, ↓), pomocí nichž je možné nahradit známé Lispovské funkce car a cdr při zpracování seznamů, které jsou v jazyku APL implementovány jako vektory. Na závěr budou popsány funkce pro manipulaci s maticemi (včetně operátorů . a ◦.) a způsob práce s řetězci.

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.