Hlavní navigace

Fortran: základní konstrukce jazyka

6. 8. 2007
Doba čtení: 10 minut

Sdílet

V dnešním povídání o Fortranu se podíváme na formát zdrojového kódu, aritmetiku, výrazy a věci okolo kontroly běhu programu - funkce a podprogramy, výkonné konstrukce (podmínky a cykly). Nakousneme také moduly - stavební bloky moderního Fortranu. Přidáme pár zajímavostí z historie.

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:

  1. v modulech
  2. jako vnořené procedury
  3. 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:

root_podpora

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.

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