Hlavní navigace

Fortran: Další plody modernizace

20. 8. 2007
Doba čtení: 11 minut

Sdílet

V tomto díle povídání o Fortranu si ukážeme, jak používat různé druhy číselných typů, řetězce, odvozené typy (struktury), generická rozhraní (přetěžování), a podrobněji rozvedeme přístupová práva a použití modulů pro stavbu programů. Povíme si také něco o tom, co nás čeká ve Fortranu 2003.

Dlouhá a krátká čísla

O základních Fortranských typech – integer, real, complex, logical a character

už víme. Ty, kteří jsou vzděláni např. v jazyku C, jistě při té příležitosti napadlo, jak lze získat např. short int, long double a jiné. Pro tyto účely Fortran zavádí mechanismus takzvaných typových druhů (kind). Ten lze uvést v závorce za typem, například integer(2), real(8). Takto lze získat různé přesnosti reálných, komplexních a celých čísel a u většiny překladačů to funguje i na logical. kind je vždy nezáporné číslo, které nějak označuje rozsah daného čísla. „Nějak“ je důležité – ve standardu totiž není přesně specifikováno, jak. Většina překladačů používá konvenci

kind= počet bajtů pro uložení proměnné, není tomu tak ale vždy.

Psát tedy ve zdrojovém kódu přímo real(4) není úplně dobré. Jaký je tedy dobrý způsob (posvěcený standardem)? Především od každého základního typu existuje alespoň jeden kind  – defaultní. Reálné existují alespoň dva, a to jednoduchá a dvojitá přesnost (ale pozor, někdy můžou splývat). Nejlepší způsob, jak programovat, je definovat si na vhodných místech celočíselné konstanty, které pak lze jako kind používat. K tomu lze použít zejména vestavěné funkce selected_real_kind a selected_int_kind, které najdou druh čísla tak, aby byly splněny požadavky na přesnost, např. alespoň 6 desetinných míst. Klasický je rovněž postup s použitím funkce kind:

integer,parameter:: dp = kind(1.d0) ! dvojitá přesnost
real(dp):: a_variable

kind vrací druh proměnné nebo literálu. Zápis reálných čísel s d  místo e totiž automaticky znamená dvojitou přesnost.

U literálu specifikujeme druh pomocí podtržítka a čísla nebo konstanty: 1._dp,12345_4. Poznamenejme ještě, že reálné druhy lze používat i jako komplexní a celočíselné často fungují jako logické. Nejčastěji jsou druhy potřeba pro reálná čísla, jinak si asi obvykle vystačíte s defaultními. Velmi důležitý je ale tento mechanismus pro spolupráci s C, o které se ještě zmíníme. Druh lze specifikovat i u znakových proměnných (např. pro podporu Unicode). Nevím sice o tom, že by to nějaký překladač podporoval, ale teoreticky tu ta možnost je.

Povídající programy

S řetězci sice ve výpočtových programech zase tolik pracovat nepotřebujeme, nicméně tvoří povinnou výbavu programovacího jazyka. Deklarují se pomocí typu character s délkovým parametrem ( len), označujícím délku řetězce. V tom je hlavní nedostatek řetězců ve Fortranu 95: mohou být statické a automatické, ne však dynamické (viz terminologie polí v minulém díle). Příklady:

character(256):: jmeno_souboru
character(pocet_cislic+8):: cislo_str ! v proceduře

Tento nedostatek byl odstraněn ve Fortranu 2003 (kde logicky souvisí s dalšími vylepšeními). S automatickými a statickými řetězci si ale pro jednoduché věci vystačíte, a ty dynamické se dají v případě potřeby v současných překladačích „simulovat“, jen je to o malinko méně pohodlné. Do řetězcové proměnné je možné přiřadit řetězcový výraz jiné délky. Je-li výraz delší,ořízne se, je-li kratší, doplní se mezerami. K práci s řetězci slouží nám již známý operátor zřetězení //, možnost specifikovat souvislé podřetězce (podobně jako sekce polí, např. jmeno_souboru(5:20)), dále vestavěné funkce trim, len_trim, scan, adjustl a několik dalších, vesměs pomocného charakteru. Fortran není zrovna zaměřen na práci s řetězci, takže většinu sofistikovanějších funkcí si musíte naprogramovat sami. Je třeba si uvědomit odlišnost řetězců a polí znaků:

 character(n):: str
 character:: str(n)

Používat je samozřejmě možné obojí, ale druhý způsob (pole znaků) má vlastnosti pole, tedy nefunguje např. automatické ořezávání. Na druhou stranu lze ale používat nesouvislé sekce nebo udělat pole dynamické. To je možnost, jak simulovat dynamické řetězce. To lze buď explicitně (převádění obou forem mezi sebou je možné např. pomocí funkce transfer nebo přes explicit shape a assumed size argumenty), nebo využít některou implementaci standardního modulu ISO_VARYING_STRING, např. www.fortran.com/i­so_varying_strin­g.f95 (interface tohoto modulu je součástí standardu, takže lze říct, že Fortran 95 vlastně dynamické řetězce má, jen ne ty „nativní“). Šikovná je možnost použít jako délku řetězce hvězdičku (tzv. assumed length), a to buď u formálních argumentů, nebo u konstant. V prvním případě se do procedury automaticky předává délka řetězce (podobně jako u assumed shape polí), ve druhém případě se délka konstanty určí z inicializačního literálu, tedy např.

character(*),parameter:: err_cannot_open_file = 'cannot open file.'

což je samozřejmě pro řetězcové konstanty velmi užitečné.

V kompaktním balení

Jedna z věcí, které ve Fortranu 77 chyběly nejvíce, byly struktury, tedy složené typy. Také to byl velmi častý námět různých rozšíření. Fortran 90 tento nedostatek odstranil zavedením tzv. odvozených typů (derived types). Ty se deklarují v modulu nebo proceduře takto:

type point
  real:: x(3),r
  integer:: index
end type

Taková struktura se pak může používat v deklaracích jako typ proměnné:

type(point):: p0,p(n)

Ke komponentám lze přistupovat stylem p0%x,p0%index (komponenty skalární proměnné) nebo p%r (skalární komponenta proměnné pole). Nelze používat p%x, tj. buď komponenta nebo proměnná musí být skalár, zřejmě z důvodů jednoduchosti. Osobně bych ale tuto možnost uvítal. Komponentami struktur mohou být skaláry nebo pole s konstantními rozměry, ukazatele (o těch za chvíli), a s TR 15581 (viz první díl) také alokovatelná pole. Deklarace komponent mají tvar

typ[,atributy]:: seznam komponent = inicializace

Je-li uvedena inicializace, jde o defaultní hodnotu komponenty při vytvoření jakékoliv proměnné, např. při vstupu do procedury, alokaci pole atd. Samozřejmostí je použití strukutr jako argumentů nebo návratové hodnoty funkce. Hodnoty odvozených typů lze explicitně vytvářet pomocí konstruktorů struktur: p0 = point(pos,sqrt(sum(pos**2)),i). Ve Fortranu 95 je nutné specifikovat všechny komponenty, Fortran 2003 umožňuje specifikovat jen některé.

Ukažme si na ně

Dalším závažným nedostatkem Fortranu 77 byla nemožnost někam uložit odkaz na nějakou proměnnou. To opět řeší Fortran 90 zavedením ukazatelů. Ty se definují tak, že nějaké proměnné dáme atribut pointer, například:

real,pointer:: a,b(:,:)

Lze definovat ukazatele jak na skaláry, tak na pole. S proměnnou s atributem pointer lze provádět všechno co s jinými proměnnými (jako by se ukazatel automaticky dereferencoval). Kromě toho je lze asociovat s jinými proměnnými v ukazatelovém přiřazení (pointer assigment):

a => cíl
b => cíl

proměnná cíl musí mít atribut pointer nebo target, což je atribut, který označuje proměnné, na které mohou ukazovat ukazatele. V případě ukazatele na pole může být cíl libovolná sekce (ale ne s vektorovým indexem). Kromě toho lze také ukazatel vynulovat pomocí příkazu nullify, zjistit, zda je asociován s libovolným nebo konkrétním cílem funkcí associated. Také jej lze alokovat příkazem allocate, čímž se vytvoří anonymní proměnná, se kterou se ukazatel asociuje, a dealokovat příkazem deallocate.

Při práci s ukazateli ve Fortranu 95 ale nemáte takovou svobodu jako v C. Především, jak už bylo řečeno, nelze ukazovat na libovolné proměnné, což je ale spíše dobré kvůli optimalizacím. Nedostatkem ovšem je plná typová kontrola – ukazatele nejde jednoduše přetypovávat jako v C a nic takového jako generický ukazatel void * neexistuje. To někdy komplikuje situaci při tvorbě knihoven, kde bychom potřebovali pracovat s generickými daty. Existují sice triky, jak se s tímto problémem vypořádat, ale nejsou zdaleka tak elegantní a efektivní jako přetypování void * v C. Dalším nedostatkem je, že nelze vyrobit ukazatel na proceduru (přestože lze procedury předávat jako argumenty). Oba tyto nedostatky řeší Fortran 2003 (tyto schopnosti najdeme již v překladačích G95 a Sun, v GCC jsou na cestě).

Přetěžování

Při volání procedur musí souhlasit typ argumentů, včetně druhů. Ve Fortranu 77 bylo tedy třeba, podobně jako v C, používat jiná jména pro matematické funkce na různých typech a typových druzích, např. ABS,DABS,IABS pro absolutní hodnotu reálného čísla v jednoduché přesnosti, dvojité přesnosti, a celého čísla. Podle mě je to situace trochu nedůstojná vědeckého jazyka, a jedním z nejčastějších rozšíření Fortranu 77 byla právě možnost psát sin(x) i pro dvojitou přesnost x (místo dsin(x)). Fortran 90 tuto možnost opět standardizoval, a šel mnohem dále – zavedl takzvaná generická rozhraní (generic interfaces), která jsou velmi blízká přetěžování funkcí a operátorů z C++ (ačkoliv mechanismus je trochu jiný). Generickou proceduru definujeme (obvykle v modulu) stylem:

interface generické_jméno
  module procedure jméno1
  module procedure jméno2
  hlavičky externích procedur
end interface

Čili do generického rozhraní můžeme sdružovat procedury definované v modulech (ke kterým máme přístup) nebo externí procedury (např. i nenapsané ve Fortranu). Ve druhém případě musíme opsat hlavičky procedur, stejně jako jsme si ukazovali ve druhém díle. Jednotlivé procedury takto sdružené se musí dostatečně lišit ve svých argumentech. Rozhodující je tzv. TKR (Type,Kind,Rank) kompatibilita. V zásadě jsou pravidla nastavena tak, aby při generickém volání (funkce nebo podprogramu) generické_jméno(argumenty) bylo možno určit specifickou proceduru podle argumentů. Je možné mixovat podprogramy a funkce, a funkce je odlišitelná od podprogramu se stejnými argumenty (jak už víme, volají se jinak). Nelze ale rozlišit funkce podle typu návratové hodnoty, to by znamenalo nejednoznačnost. Procedury mohou být i elementální, pak lze použít i generické jméno jako elementální.

Jak už bylo naznačeno, je možné také přetěžovat operátory, a to tak, že použijeme

interface operator(+)

kde operátor může být libovolný vestavěný operátor ( +,-,*,/,=,/=,<=,>=,.and.,.or.,.not.,.eqv.,.neqv.,//) nebo uživatelsky definovaný definovaný ( .jméno.). Použití se nijak neliší od funkcí (pouze infixovou notací), takže jde vlastně o „syntaktický cukr“. Nelze tak ale předefinovat vestavěné operace na základních typech (ty jako by v generickém rozhraní byly automaticky přítomny). Lze také přetížit přiřazení (které ve Fortranu není operátor):

interface assignment(=)

a v tomto případě se samozřejmě defaultní přiřazení (které existuje pro každý typ) předefinuje.

Šablony?

Znalce jazyka C++, kteří teď na něco čekají, zklamu: Druhý „cépluskový“ schodek ke generičnosti a metaprogramování, tedy šablony, Fortran nemá. A ani v dohledné době mít nebude. Neexistuje totiž širší shoda mezi uživateli a tvůrci, jak a zda vůbec by se to mělo udělat. Pro metaprogramování (což zahrnuje klasické použití šablon pro tvorbu typově generických procedur) lze používat různé makroprocesory, a nemálo uživatelů Fortranu si myslí, že by to tak mělo zůstat. V návrhu Fortranu 2008 byla původně tzv. inteligentní makra, později (minulý měsíc) byla ale vypuštěna (zejména proto, že Fortran 2008 má být malou inovací podobně jako Fortran 95). Osobně používám úspěšně pro metaprogramování ve Fortranu makroprocesor m4, který najdete ve většině unixových prostředí, takže mi tato schopnost také nijak zvlášť nechybí.

Ještě o modulech

Jak už víme, základními stavebními bloky programů jsou moduly, které poskytují úložiště pro konstanty, struktury, proměnné, procedury a rozhraní. Moduly se používají klauzulí use, která v nejjednodušší podobě vypadá takto:

use modul

Tu lze použít na začátku jiného modulu nebo procedury (nebo bloku program), a tím se zpřístupní všechny entity z příslušného modulu. Přesněji řečeno, všechny veřejné entity. V modulu totiž můžete specifikovat přístup k entitám příkazy

public [seznam jmen]
private [seznam jmen]

Vynecháme-li seznam jmen, znamená to nastavení pro ostatní entity (ty, které nejsou v tomto modulu nastaveny jmenovitě). Default je public. U proměnných, konstant a typů lze public a private použít i jako atributy. Fortran 2003 přidává přístup protected, což znamená „zpřístupnit jen pro čtení“.

Moduly také předcházejí konfliktům jmen. Každý modul má svůj vlastní „prostor jmen“, tedy můžeme mít v každém modulu proceduru init a nevadí to. Z modulu můžeme importovat jen selektivně:

use modul,only: jméno1[,jméno2...]

Pokud už v lokálním modulu stejné jméno existuje, lze při importu entitu přejmenovat:

use modul,[only:] lokální jméno => jméno, ...

Importované entity se z modulu automaticky i exportují, pokud nedostanou přístup private. Jeden modul tak může třeba jen sdružovat jeden nebo několik modulů a exportovat vybrané entity. Jedna entita může být dostupná pod více jmény, a může být i několikrát importovaná pod stejným jménem. Nesmí se však stát, že bychom pod stejným jménem importovali dvě různé entity, to by se překladač zlobil (i když by to byly např. konstanty stejné hodnoty).

Fortraní moduly jsou dobrým nástrojem pro organizaci programů, takže se doporučuje jich hojně využívat, např. pro tvorbu knihoven. Standard nepředepisuje nic o souborech a odděleném překladu, uvádí pouze doporučení. Všechny překladače, které znám, to ale řeší tak, že umožňují oddělený překlad a informace o veřejných entitách modulů ukládají do speciálních souborů s příponou .mod nebo .MOD, takže je možné je použít v jiném zdrojovém souboru. Ty jsou někdy textové a někdy binární, ale vždy obsahují částečně předkompilovaná data. Překladače proto zvládají efektivně i rozsáhlé stromy modulů (tisíce), takže se nemusíte stydět vytvořit samostatný modul pro jednu či několik konstant (a to je někdy i nejlepší řešení).

Než přišly moduly, tedy do Fortranu 77 včetně, se hojně využíval příkaz include, fungující podobně jako direktiva #include v C, tedy vložení jiného zdrojového souboru. Ve většině případů jej nahrazují pohodlnější a flexibilnější moduly, ale sem tam pro něj najdete nějaké uplatnění.

CS24_early

Co přinese Fortran 2003?

Fortran 2003 je opět velkou revizí standardu (komise zřejmě plánuje dělat střídavě velké a malé revize). Jak již bylo řečeno, není zatím plně podporován žádným překladačem, jednak asi kvůli množství novinek, které přináší, a také kvůli větší konzervativitě jeho uživatelů, a s tím spojené nižší poptávce po nových schopnostech jazyka. Snad nejžádanější novinkou Fortranu 2003 je „interoperability with C“, která umožňuje lépe a pohodlněji vytvářet smíšené C/Fortran programy, a dělá pořádek v již existujících praktikách a rozšířeních. Kromě toho sama o sobě umožňuje některé věci, které se dřív musely složitě obcházet. K dalším novinkám patří např. procedurální ukazatele, rozšíření typů a typově vázané procedury (pro objektově orientované programování), automatická alokace polí (moje oblíbená), parametrizované struktury, rozšířená podpora IEEE atd. Snad nejdál je s podporou Fortranu 2003 překladač NAG, kde už je skoro všechno.

Příště

V příštím (zřejmě posledním) díle ještě stručně shrneme I/O, a pak si uděláme slíbený delší výlet do historie Fortranu. Zamyslíme se taky nad jeho místem v dnešním světě mezi ostatními jazyky a jeho dlouhodobější budoucností.

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

Autor článku