Zdrojový kód a dědictví děrných štítků
Ukázka kódu ve Fortranu byla k vidění v minulém díle a nejspíš jste na ní nezpozorovali nic podivného. Je to proto, že ukázka byla psána moderním, tzv. volným formátem Fortranu. Ten víceméně odpovídá tomu, co známe i z jiných jazyků – alfabetické tokeny se oddělují mezerami, počet mezer nerozhoduje, řádky lze libovolně odsazovat. Celý Fortran je case insensitive, tedy velká a malá písmena jsou záměnná. Žádný speciální znak pro konec příkazu Fortran nemá – příkaz obyčejně končí s koncem řádku. Speciální znak &
musíme naopak použít, pokud chceme rozdělit příkaz na více řádků. To je občas nezbytné, protože délka řádku je ve Fortranu omezena na 132 znaků a mnozí programátoři se omezují ještě více. To je pochopitelné. I když třeba v C není délka zdrojového řádku omezena, nikdo je nedělá 200 znaků dlouhé.
Fortran 2003 (stejně jako předchozí standardy) zná ale ještě další formát – pevný formát. Ten byl nativní pro Fortran 77 a byl navržen ještě pro děrné štítky. Od Fortranu 90 je sice označen jako zastaralý, ale stále máte velkou šanci se s ním setkat u starších knihoven, zejména ve Fortranu 77.
Jak to poznáme? Nejrozšířenější je konvence, že soubory s příponou .f90
jsou ve volném formátu a soubory končící na .f
v pevném formátu. Nedá se na to ale spolehnout. Bezpečněji se to pozná podle odsazení – pokud všechny řádky kromě komentářů a návěští začínají až v 7. sloupci, jde zřejmě o pevný formát. Je třeba také říci, že lze psát kód tak, aby vyhovoval oběma formátům zároveň. Někteří programátoři to dělají. Budete-li však s Fortranem experimentovat, doporučuji vám na pevný formát zapomenout.
FORmula TRANslator
Pokud jde o základní aritmetiku – proměnné, přiřazení a výrazy, neliší se Fortran příliš od jiných jazyků. Proměnné se deklarují stylem
typ[,atributy]:: seznam proměnných
Základní typy jsou integer
, real
, complex
, logical
a character
. Ve Fortranu 77 by to byl (spolu s double precision
) vyčerpávající přehled, Fortran 90 portfolio typů výrazně rozšířil, ale o tom až jindy. Z atributů si v této chvíli uvedeme jen parameter
, umožňující deklarovat konstanty:
integer,parameter:: pocet_Krokovych_dcer = 3 real,parameter:: pi = 3.14159265, pi_hrube = 3.14, pi_biblicke = 3.
Celočíselné literály se zapisují jak jsme zvyklí odmalička, reálné musí obsahovat desetinnou tečku nebo exponenciální část (nebo oboje), komplexní se píší jako dvojice (reálná část, imaginární část)
. Logické hodnoty jsou .true.
a .false.
, řetězce se uzavírají do apostrofů nebo uvozovek. Přiřazení má tvar proměnná = výraz
. Základní operátory +-*/
fungují jako v jiných jazycích, podíl dvou celých čísel znamená celočíselné dělení. Operátor umocňování je **
(jako v Pythonu nebo Octave), //
je operátor spojování řetězců. Relační operátory jsou <, >, <=, > =, ==, /=
nebo „postaru“ .lt., .gt., .le., .ge., .eq., .ne.
, logické operátory jsou .and., .or., .not., .eqv., .neqv.
. Kromě toho Fortran nabízí velkou kolekci vestavěných funkcí, mezi nimiž nechybí standardní matematické funkce. Samozřejmostí je možnost závorkování.
Příklady:
E = m * c**2 area = span * (broot + btip)/2 drag = 0.5 * rho * v**2 * area * (cDv + cDi) c = sqrt(a**2 + b**2 - 2*a*b*cos(gamma)) curvature = (dx**2+dy**2)**(-1.5) * abs(d2x*dy-d2y*dx) 1**h / rva == b**k**pi
Chceš-li pomoc, zavolej
Na úsvitu počítačového věku, v dobách, kdy se programovalo bezezbytku v assembleru nebo strojovém kódu, se programy sestavovaly jako umělecká díla – jeden velký, provázaný kód, v němž každý bajt měl svůj účel, nebo i několik účelů. To platilo i pro úplně první Fortran I. Teprve Fortran II přinesl podprogramy, a položil tak základ procedurálnímu programování.
Fortran zná dva druhy procedur – podprogram (subroutine) a funkci (function). Podprogramy se volají způsobem
call jméno_podprogramu(argumenty)
zatímco funkci zavoláme tak, že prostě v nějakém výrazu specifikujeme jméno_funkce(argumenty)
.
Důvod tohoto silného rozlišení je ten, že pro vyhodnocování výrazů a funkcí v nich platí ve Fortranu poněkud jiná pravidla než v C (a jeho potomcích). V C mohou funkce ve výrazech dělat cokoliv, protože lze přesně určit pořadí, v jakém se budou vyhodnocovat. Ve Fortranu, naproti tomu, je zakázáno, aby volání funkce ve výrazu ovlivňovalo nějakou jeho jinou část, protože překladač smí změnit pořadí vyhodnocení operandů nebo argumentů funkcí, a dokonce je nemusí vyhodnotit vůbec, pozná-li, že to už není třeba. To jsou samozřejmě pravidla velmi přátelská pro optimalizující překladač, ale uživatele zvyklého např. na C mohou někdy nepříjemně zaskočit. Dobrým zvykem je tedy definovat všechny procedury s podstatnými vedlejšími efekty jako podprogramy. Poznamenám zde, že funkce i podprogramy lze označit jako pure
, což znamená, že jsou zcela bez vedlejších efektů.
Jak se tedy procedury definují? Podprogramy se definují následovně:
subroutine sub1(a,b,c) integer,intent(in):: a real,intent(out):: b complex,intent(inout),optional:: c end subroutine
Hlavička, jak vidíme, obsahuje seznam jmen formálních argumentů (dummy arguments), pak následují deklarace. Možná jste už odhadli, že intent
slouží k označení druhů argumentů z hlediska změny: vstupních ( in
), výstupních ( out
) a „průtokových“ ( inout
). Argumenty in
se v proceduře nesmějí definovat, argumenty out
naopak musejí, u inout
je to jedno. Atribut optional
označuje volitelný parametr – ten se nemusí při volání procedury specifikovat. Nemá pak žádnou hodnotu a nemůžeme s ním dělat nic, jen zjišťovat jeho přítomnost pomocí funkce present
nebo jej použít jako argument pro jinou proceduru s optional
argumentem. (To je rozdíl oproti C++ nebo Pythonu, kde se specifikují defaultní hodnoty argumentů. Je to ovšem logické, když si uvědomíte, že ve Fortranu se obvykle předávají všechny parametry odkazem – a navíc, jak byste vymýšleli defaultní hodnotu pro desetiprvkové pole?) O dalších atributech se zmíníme později. Šikovnou schopností Fortranu je také volání parametrů funkce jmény, např:
call sub1(1,c=moje_c,b=b)
To se vyplatí zejména u složitých výpočetních procedur s hodně volitelnými argumenty. Funguje to obdobně jako např. v Pythonu – libovolný počet parametrů bez jmen na začátku se přiřadí podle pozice, ostatní podle jména.
Funkce začínají hlavičkou:
function fun1(a,b,c) result(x)
Jinak je to stejné jako u podprogramů, jen kromě formálních argumentů zde musíme ještě deklarovat typ návratové hodnoty x
(ta nemá intent
). Vynecháme-li result
, má návratová hodnota jméno shodné se jménem funkce. Kvůli výše zmíněným restrikcím na vyhodnocování funkcí doporučuji vytvářet funkce jen s argumenty intent(in)
.
Kam s nimi?
Teď, když už zhruba víme, jak se procedury definují, by bylo dobré vědět, kde to můžeme udělat. A to můžeme:
- v modulech
- jako vnořené procedury
- jako externí procedury
Nejběžnější je první možnost, tedy v modulech. Modul vypadá takto:
module muj_modul implicit none ! tohle už známe, není to nutné ! konstanty, proměnné, složené typy, rozhraní (interfaces) ! určení přístupových práv contains ! procedury - podprogramy a funkce end module
Procedury (jakož i ostatní objekty) z modulu pak můžeme zpřístupnit jinému modulu, proceduře nebo bloku program
pomocí klauzule (následuje hned za úvodním řádkem)
use muj_modul
Další možností je definovat vnořenou proceduru jiné proceduře nebo bloku program
(který hraje ve Fortranu podobnou úlohu jako v C funkce main). To se dělá opět umístěním klíčového slova contains
za tělo procedury, a následují definice vnořených procedur:
subroutine materska ! deklarace ! příkazy contains subroutine vnorena ! příkazy end subroutine end subroutine
Vnořené procedury lze volat jen z jejich mateřské procedury (nesmí se ani „propašovat“ jako argument do jiné procedury) a mají přístup ke všem jejím lokálním proměnným. To je poměrně užitečná schopnost, se kterou se dá kód složitějších procedur dost zpřehlednit a zjednodušit.
Poslední možností je definovat externí proceduru. To se dělá tak, že ji zapíšeme jako samostatnou programovou jednotku – tedy na úrovni program
nebo module
. To je způsob známý z Fortranu 77 (kde byl jediný možný). Nevýhodou je to, že jméno pak musí být unikátní v celém programu (podobně jako v C) a že volající kód nemá automaticky informaci o počtu a typu parametrů. Tu mu můžeme dodat pomocí bloku rozhraní (o těch se ještě zmíníme), nebo ji nechat odvodit překladač (tzv. implicit interface) – to ale při použití některých moderních druhů argumentů nejde. V moderním Fortranu se doporučuje tento způsob využívat výjimečně a jinak „skladovat“ procedury v modulech.
A teď si posvítíme na těla procedur, tedy výkonné konstrukce.
Na počátku bylo goto
To není tak docela pravda. Už první IBM Fortran I obsahoval indexovanou smyčku do
. Ne zcela v té podobě, jak ji zná Fortran dnes:
do index=dolní mez,horní mez[,krok] příkazy end do
Namísto koncovky end do
se konec bloku označoval číselným návěštím. Návěští se vůbec používala mnohem více. Příkaz if
totiž existoval jen v jedno-příkazové formě:
if(podmínka) příkaz
přičemž příkaz
byl nejčastěji goto
. Moderní Fortran, jak už víme, zná navíc i blokový tvar
if(podmínka) then příkazy else if(podmínka) then příkazy else příkazy end if
(větví else if může být i více). Moderní Fortran došel po cestě k eliminaci goto
ještě o kus dál – především má další dva druhy smyčky: Podmínkovou
do while(podmínka) příkazy end do
a také „nekonečnou“
do příkazy end do
uvnitř kterékoli smyčky lze používat příkazy exit
a cycle
. První z nich ukončí celou smyčku, druhý ukončí aktuální cyklus (a pokračuje dalším cyklem smyčky). V případě vnořených smyček je užitečná možnost si smyčky pojmenovat:
jméno:do
a pak označit pomocí exit jméno
nebo cycle
jméno
, na kterou smyčku se má příkaz vztahovat (jinak jde o nejvnitřnější smyčku). Další výkonnou konstrukcí je
select
:
select case(výraz integer/logical/character) case hodnota1 příkazy case hodnota2:hodnota3 příkazy case default příkazy end select
tedy obdoba příkazu switch v jazyce C. Oproti jazyku C zde není možnost překrývání a „propadávání“ větví -vždy se provede nejvýše jedna. Na druhou stranu je tu možnost specifikovat pro jednotlivé větve jednostranné i oboustranné rozsahy hodnot.
Navzdory všem vymoženostem strukturovaného programování příkaz goto
ve Fortranu stále existuje. Má tvar:
goto číslo
Čísla návěští jsou lokální pro proceduru a mohou uvozovat většinu příkazů. Je ale dobrým zvykem, když už návěští (a goto
) musíte použít, dávat je jen příkazům continue
, což je prázdný příkaz (tj. nedělá nic).
Zajímavosti z dávných dob
V pevném (fixed) formátu zdrojových kódů se úplně ignorovaly mezery. Jazyk byl postaven tak, že i přesto nedocházelo k dvojsmyslům. Někdy to ovšem bylo těsně. Slavným příkladem je záměna čárky za tečku v kódu typu:
DO 10 I=1,50 ... 10 CONTINUE
(o tomto starším tvaru do
jsme se zmínili výše). Zaměnila-li se čárka v prvním řádku za tečku, interpretoval překladač řádek jako přiřazení DO10I = 1.50
, díky implicitnímu typování proměnnou DO10I
automaticky vytvořil, a výsledkem byl platný kód se zcela jiným smyslem.
Výše uvedená vlastnost pevného formátu má jeden zajímavý důsledek: koncové příkazy bloků (např. end do
, end subroutine
) lze psát i dohromady, tedy enddo
, endsubroutine
. Důvodem je, že ještě před zavedením volného formátu se u již existujících bloků používaly oba tvary (a bylo to totéž, protože mezery se ignorovaly), a tak byly z důvodů kompatibility oba tvary ponechány. U nových příkazů byla pak tato dvojí syntax povolena rovněž, aby to bylo konzistentní.
Zmínili jsme se, že ve starších Fortranech existoval pouze jednořádkový podmínkový příkaz:
IF(podmínka) příkaz
To platilo ve Fortranu IV a Fortranu 66. Předtím byla situace ještě podivnější – používal se tzv. trojcestný IF:
IF(výraz) návěští1,návěští2,návěští3
v němž se podle znaménka výrazu (záporné, nula, kladné) provede skok na příslušné návěští. Podobnou „vychytávkou“ byl i tzv. computed goto:
GOTO (seznam návěští) integer-výraz
Zde se podle hodnoty celočíselného výrazu skočí na příslušné návěští. Pokud je výraz menší než 1 nebo větší než počet návěští v závorce, příkaz neprovede nic. Díky této výrazné orientaci na skoky a návěští pak vznikaly tzv. špagetové kódy, ve kterých vyznat se bylo skutečným tajným uměním. Nezaslouží si tehdejší programátoři, kteří s těmito nástroji dokázali např. vyslat člověka na Měsíc, náš obdiv?
Příště
Příště se už vrhneme na práci s poli, což je zřejmě oblast, která je na moderním Fortranu nejvíce „vidět“. Pozastavíme se zlehka nad otázkami rychlosti a optimalizací a pokusíme se zbořit (nebo alespoň uvést na pravou míru) některé mýty o Fortranu v této oblasti.