Už vím proč - nemusím použít FOR

Reference nebo studie

Po přečtení prvního dílu našeho seriálu možná zjistíte, že i ty zdánlivě nejjednodušší věci jsou daleko složitější, než vypadají a že cyklus for se občas používá nadbytečně.

Člověk, co ledacos v programování už zažil, se může ohlédnout, jak se "pod hlubším povrchem" v různých programovacích jazycích řeší taková "samozřejmost", jako je jedna z nejzákladnějších věcí v programování, a to obyčejný cyklus. Po přečtení tohoto článku možná zjistíte, že i ty zdánlivě nejjednodušší věci jsou daleko složitější, než vypadají a že cyklus for se občas používá nadbytečně. V dalším článku pak vytvoříme pomocí OOP návrhového vzoru Interpreter  jednoduchý interpret cyklu for BASICu ze světa osmibitů v C#. Postupně bych se chtěl v seriálu propracovat od problémů psaní kódu k analýze textu, k neuronovým sítím a k dnešní "umělé inteligenci" co až tak inteligentní není.
U všech příkladů se zdrojovým kódem je odkaz i na online prostředí, všechny příklady tak lze pohodlně vyzkoušet v prohlížeči.

Cyklus for v JSA

Psaní v JSA (=jazyk symbolických adres, "assembly language") je pouze pro silné povahy... Tento příklad zkompilovatelný pomocí nasm na ("dnešním") Linuxu kód vypíše čísla 0 - 9 na terminál.

section .text

   global _start  ;Povinné pro linker (ld)

_start:           ;linker vstupní bod

   mov ecx, 10      ;Konečná pozice v registru ecx pro testování loop

                    ;ECX je známý jako početní registr

l1:                 ;Návěstí l1 pro skok na počátek opakování

   push ecx         ;Uložím obsah registru ecx na zásobník,

                    ;protože jinak ztratím informaci o cyklu


   mov ecx, msg     ;Co budu tisknout přesunu celkem "logicky"
                    ;z proměnné msg do "početního" registru ecx

                    ;Tím jsem ovšem přepsal hodnotu pro testování cyklu!

   mov eax, 4       ;Do registru eax číslo 4

                    ;Znamená system call number (sys_write)

   mov ebx, 1       ;Do registru ebx 1 číslo 1

                    ;Znamená file descriptor (stdout)

   mov edx, 2       ;Je potřeba i velikost toho, co budu tisknout, dva znaky

   int 0x80         ;Tisknu hodnotu pomocí naprosto pochopitelného volání

                    ;funkce jádra Linuxu

   inc byte [msg]   ;Zvýším hodnotu prvního byte msg,

                    ;tj. posunu se na další znak. Například 0->1

   pop ecx          ;Obsah registru ze zásobníku rychle zpátky,

                    ;přijde testování loop

loop l1             ;Tady se točíme a skoro vidíme,

                    ;že se přitom testuje registr ecx 

mov eax, 1          ;system call number (sys_exit)

int 0x80            ;call kernel

section .data

msg DB 48, 0xa ;Definování dvouznakového řetězce, do "proměnné" msg

               ;uložím char '0' = (48 v ASCII tabulce) a znak nového řádku.

               ;Jde zapsat i jako msg DB '0', 0xa

Pro zájemce jsou podrobnosti popsané v kódu. Samozřejmě kód má řadu nectností (moc daleko dál než do čísla 9 se se ním nedostanete). V JSA neprogramuji, sbíral jsem trosky svých znalostí z osmibitů a krátkého VŠ kurzu, šlo mi pouze o co nejpochopitelnější demonstraci fungování programování v JSA. I v tak krátkém kódu je vidět, jak se musí programátor starat o registry a jak je takové programování "přehledné" a "pohodlné". Stačí dát místo "mov ecx, 10" hodnotu "mov ecx, 0" a je hned o nekonečnou cyklickou zábavu postaráno.

Online programování:

https://www.tutorialspoint.com/compile_asm_online.php

Další zdroje (a inspirace):

https://www.tutorialspoint.com/assembly_programming/assembly_loops.htm

Jazyk C a cyklus for

Komu by se tak chtělo programovat něco složitějšího v JSA (assembly language) z předchozího příspěvku... Podle zásady vikinga Vika, že "největší lenoši vymysleli největší vynálezy" se začal vyvíjet v Bellových laboratořích AT&T mezi léty 1969 a 1973 nový jazyk C pro ulehčení programátorské práce. V roce 1973 se jazyk C stal dostatečně stabilním pro nasazení v praxi. Většina zdrojového kódu jádra Unixu, původně napsaného v assembleru PDP-11, byla přepsána do C. Dokáže si dnes někdo představit ladit původní kód jádra v JSA? Posun v pohodlí a přehlednosti je velmi znatelný, náš cyklus for se redukuje na dva řádky a nějaké ty závorky:

#include <stdio.h>

int main() {

  for (int i = 0; i < 10; i++) {

    printf("%d\n", i);

  }

}

Online programování:

https://onecompiler.com/c

"Neprogresivní" programovací jazyky a cyklus FOR

S příchodem osmibitových počítačů (mikropočítačů) s dnes neuvěřitelnou kapacitou RAM v kB a frekvencí v MHz (viz tabulka 1 níže) v osmdesátých letech přišla i éra jednoduchého interpretovaného programovacího jazyka BASIC (Beginner's All-purpose Symbolic Instruction Code), který jak pozitivně, tak i negativně ovlivnil celou generaci programátorů. Jedná se o jednoduchý jazyk pro začátečníky (vždyť to má i v názvu jako "Beginner's"), ve kterém šlo velmi lehce naprogramovat třeba zobrazení grafu nebo řešení kvadratické rovnice. A vesele přitom používat teoretiky zatracovaný příkaz GOTO...

Bohužel smůlu měli ti programátoři, kterým nestačilo jen výše zmíněné programování grafů a řešení primitivních matematických úloh, ale chtěli i něco víc - například vytvářet pro tyto „báječné osobní počítací stroje" nové "graficky úžasné" a "perfektně rychlé" akční hry. Interpretovaný programovací jazyk byl v těchto počítačích velmi pomalý (Atari nemělo v originálním BASICu ani podporu počítání s celými čísly, vše se odehrávalo v pohyblivé řádkové čárce) a zabíralo často zbytečně paměť, ze které byl na osmibitech potřeba každý byte. S pokorou se tak museli tito programátoři vrátit k JSA. K jazyku, kterého se chtěli jejich línější kolegové zbavit už na začátku sedmdesátých let.

 

Procesor

Volná RAM

Zjištění volné paměti

ZX Spectrum 48K

Z80A (Zilog)
3,5 MHz

41 473 bytů

PRINT 65536 - USR 7962

Atari 800XE

MOS 6502
1,77 MHz

37 902 bytů

PRINT FRE(0)

Commodore C64

MOS 6510
0,985 MHz

38 909 bytů

PRINT FRE(0) + 65536

Tabulka 1: Nejrozšířenější osmibitové počítače v ČSSR, jejich procesor a paměť pro programování v BASICu. Zajímavé, že ten nejhorší a nejlevnější počítač s gumovou klávesnicí a s nejmenší pamětí RAM měl u procesoru nejvyšší kmitočet a nejvíc místa pro programování v BASICu.

Basic a cyklus for

Zápis cyklu for v jazyce BASIC je ještě jednodušší a pochopitelnější než jeho varianta v jazyce C. Pokračujme v našem příkladu a zkusme zapsat opakování čísla "od 0 do 9".

10 FOR I=0 TO 9

20   PRINT I

30 NEXT I

Docela jednoduché, že? Zajímavější věci se začnou dít, pokud čísla otočíme. Podíváme se na stejně zapsaný cyklus for na třech nejrozšířenějších osmibitových počítačích v ČSSR osmdesátých let z tabulky 1.

10 FOR I=9 TO 0

20   PRINT I

30 NEXT I

Který počítač vrátil správný výsledek?

ZX Spectrum - nevypíše nic.

Atari 800XE + Commodore C64 vypíše číslo 9.

A jak je možné, že stejný kód vrátí odlišný výsledek? Logicky správnou odpověď vrátil ZX Spectrum. V počítačích "Atari 800XE" a "Commodore C64" program vstoupí do cyklu vždy a překročení podmínky testuje až na závěr. Interpret si tak tvůrci ulehčili a příkaz NEXT se chová jako podmínka s GOTO. Jak vidíme, některé jazyky BASIC jsou kompatibilnější než jiné.

Online programování:

https://jsspeccy.zxdemo.org/

https://c64online.com/c64-online-emulator/

https://eahumada.github.io/AtariOnline/basic/basic-mame.html

Další zdroje:

https://www.root.cz/clanky/programovaci-jazyky-pouzivane-na-platforme-osmibitovych-domacich-mikropocitacu-atari/

Microsoft a BASIC

Velkým zastáncem jazyka BASIC byl Bill Gates, zakladatel Microsoftu. Aby ne, vždyť Microsoft vyvinul BASIC interpret, například pro Altair 8800 microcomputer. Microsoft měl vytvářet i ATARI BASIC, ale jeho iterpret byl na 8 kB moc velký. Pokud "pro jistotu" vyzkoušíme cyklus for z osmibitů ve VBA Excelu/VB.NET, tak se naštěstí program zachová korektně a neudělá nic.

Kód pro online zkoušení VB.NET, dá se vyzkoušet v online-kompilátoru:

Module VBModule

    Sub Main()

        Dim i as Integer

        For i = 5 To 1

            Console.WriteLine("For " & i)

        Next

    End Sub

End Module


Online programování:
https://onecompiler.com/vb

"Progresivní" programovací jazyky a cyklus for

Jazyk Pascal

V sedmdesátých letech vznikl i zajímavý a teoretiky lépe snášený výukový programovací jazyk Pascal. Tento jazyk byl kompilovaný a netrpěl některými nectnostmi jazyka BASIC (hlavně se dal psát kód bez zatracovaného příkazu GOTO pomocí smyček while/do a repeat/until, které ono zatracované GOTO provádí pěkně pod jiným názvem), proto ho mnoho počítačových nadšenců ve svém hledání strukturované alternativy k BASICu používalo po přechodu na silnější počítače PC XT/AT. Byl totiž mnohem jednodušší na naučení než jazyky C/C++. Cyklus for se ovšem zapíše zcela "basikovsky", jenom navíc přibude "ukecanější" deklarace proměnné i. Příkaz A se prostě nahradí příkazem B, jinak nic nového.

Online programování:

https://onecompiler.com/pascal/

 

program Hello;

  var i: integer;

begin

   for i:=0 to 9 do

    writeln(i);

end.

Scheme

Dalším zajímavým "progresivním" programovacím výukovým jazykem je Scheme. Tento výukový dialekt Lispu (používaný pro výuku na mnoha i amerických univerzitách) se bez cyklu obejde úplně, nemá žádné výrazy určené pro zacyklení, opakování nebo jiné provádění něčeho více než jednou na obecné úrovni. Základním způsobem, jak požadované operace provést, je rekurze (jednoduchá a poměrně špatná definice rekurze je, že procedura zavolá sama sebe).

Není tak Scheme úžasný jazyk? Kolik bezesných nocí jsem s ním strávil při svém studiu na VŠ... Jak jsem byl nešťastný z jeho závorek a prefixové syntaxe... Ovšem z tohoto zápisu je vidět možné využití (anonymních) funkcí místo "skoku" na řádek programu. Rekurze se dá samozřejmě použít i v jiných programovacích jazycích a vyhnout se tak cyklu for.  

Online programování:
https://www.jdoodle.com/execute-scheme-online/

(define for

 (lambda (n e)

  (display (string-join (list (number->string n) "\n")))    

  (if (= n e)

    e

    (for (+ n 1) e))))

(for 0 9)

C++/Java/C#

Tyto jazyky záměrně vynechám, protože vychází z "neprogresivní" syntaxe jazyka C doplněné o OOP, do které postupně s postupujícím vývojem přidávají "progresivní" prvky z funkcionálního stylu.

Python

Python je v dnešní době považovaný za "progresivní" jazyk (používaný na výuku i na mnoha amerických univerzitách), i když osobně moc nechápu důvody. Horší nápad, než odsazování bloků mezerami jsem dlouho neviděl, ale budiž. I "klasický" cyklus for má Python poněkud svérázný:

for x in range(0,10):

  print(x)

Úžasná kombinace céčkovského stylu s ostrou nerovností a basicovsko/pascalovského stylu se zadanými hodnotami od - do. Při zápisu range(0,10) prostě jedeme v cyklu od 0 do 9. Smiřte se s tím, je prostě taková doba. Navíc budete velmi překvapení při používání C# metody Enumerable.Range z namespace System.Linq. Enumerable.Range(0, 10) vrátí stejně jako Python hodnoty 0 - 9, ovšem C# Enumerable.Range(5, 10) a Python range(5,10) poskytují úplně jiné výsledky (Python podle popsaných předpokladů vrátí 5-9 a C# 5-14, protože tam druhá hodnota určuje počet hodnot a ne horní limit intervalu).

Když už jsme u těch funkcí, co takhle místo bloku dat schovaného v cyklu spustit funkci na každý prvek pole? Kde pole stejně pomocí range(0,10) definujeme? Například v Pythonu takto?

numbers = [0,1,2,3,4,5,6,7,8,9]

#nebo numbers = range(0,10)

list(map(print, numbers))

Samozřejmě zobrazený příklad není nejvhodnější, ale opět jsme se zbavili funkce for, nepotřebujeme ji! Prvky pole dál nemusí být hloupá čísla od 0 - 9 a schovaná funkce nemusí být hloupé "print"... Tím se pomalu dostáváme k iterátorům (a k vysvětlení proč musíme použít v příkladu volání list a nefunguje to bez něj), dnes daleko používanější konstrukci foreach a využití funkcionálního programování při práci se seznamy. O tom ale v dalších dílech seriálu. Nejdřív si v následujícím díle vyzkoušíme vytvořit interpret BASICovského for v C#. Nebude to tak snadné, jak to vypadá... O tvorbě tohoto parseru je článek Už vím jak – vytvořit interpret co umí FOR.

Online programování:

https://onecompiler.com/python

Další zdroje:

https://www.w3schools.com/python/python_for_loops.asp

https://realpython.com/python-map-function/

Ostatní blogy