Moduli Fortran

Sottoprogrammi (procedure), moduli



La possibilità  di decomporre un problema complesso in sottoproblemi più  semplici  è   cruciale per la scrittura di software complessi e,  nei linguaggi di tipo imperativo come Fortran, C, Pascal, Basic, corrisponde alla possibilità  di definire  dei sottoprogrammi ciascuno dei quali implementi solo parte, o  meglio un sottoalgoritmo, dell' algoritmo complessivo.

Oltre a consentire di  riflettere nella struttura del  codice la struttura dell' algoritmo, i sottoprogrammi si rivelano utili  in connessione con altri punti:

Il Fortran,  per ragioni storiche, ha diversi tipi di sottoprogramma. In  questo corso ci limiteremo a function e subroutine. Inoltre,   il linguaggio permette di definire  questi sottoprogrammi in modi diversi (indipendenemente dal programma, collegati a questo da interfacce esplicite,  come sottoprogrammi interni, procedure di modulo, singole istruzioni). Qui ci limiteremo alle seguenti tre modalità
  1. dichiarazione ed utilizzo come entità  completamente indipendenti;
  2. utilizzo attraverso l' uso di interfacce
  3. procedure  di modulo
L' utilizzo raccomandato è  solo attraverso i  moduli. Tuttavia, poiché  a volte ci si trova a dover operare con sottoprogrammi già organizzati in librerie pre-compilate, verranno anche discusse le prime due modalità  di utilizzo. Le procedure interne posono risultare  estremamente utili, a volte,  ma ne ometteremo la discussione  per  ridurre la complessità  del linguaggio, almeno nella prima fase di utilizzo. Si rimanda a testi e manuali di riferimento per maggiori approfondimenti (cfr. bibliografia in coda al programma del corso).

Function e subroutine

Le differenze morfologiche tra function e subroutine sono riconducibili alle diverse modalità  di utilizzo dei due tipi di sottoprogramma.
L'utilizzo di una  subroutine avviene mediante un' istruzione (call)  in cui si chiede al sistema di eseguire il codice corrispondente alla subroutine, dopo di che il programma continua con l' istruzione seguente alla chiamata alla subroutine. La subroutine può modificare il valore di quacuno dei suoi argomenti e  quindi ritornare al programma chiamante nuovi valori ma può anche non avere argomenti e/o eseguire altri compiti (ad esempio generare output).
La function rispecchia  l' utilizzo matematico delle funzioni:  all' interno di qualsiasi espressione, può  comparire il riferimento ad una function e il nome stesso della function ne rappresenta il valore, utilizzabile direttamente nell' espressione in cui compare.
Esempi:
...
call  aaa(x1,x2,x3)
...
call bbb
...
w= 2*fun(x,y)-7*x*y
...
t = L*(ranf()-0.5)
...

I nomi aaa e bbb corrispondono a subroutine mentre fun e ranf  sono function.   Le variabili nelle parentesi dopo il nome del sottoprogramma costituiscono  gli argomenti con cui i sottoprogrammi sono chiamati.  Sia le subroutine  sia le function  possono avere zero o più  argomenti. Nel caso di function con zero argomenti (ranf,  negli esempi)  è  però  obbligatorio scrivere ugualmente la coppia di parentesi.

Nella dichiarazione di subroutine e function,  gli argomenti (chiamati anche parametri formali, all' interno dei sottoprogrammi) non rappresentano direttamente nuove variabili ma vanno considerati  come riferimenti agli argomenti con cui verrà  realmente chiamato il sottoprogramma durante l' esecuzione.  Una prima conseguenza di questo fatto è  che il nome dei parametri formali non ha nessuna attinenza coi nomi delle variabili  usate come argomenti.
Ai parametri formali occorre assegnare un tipo dati ben preciso. Questo si fa con usuali dichiarazioni di tipo dati. Però, nel caso dei parametri formali (e solo in quello) è  possibile assegnare un attributo intent al tipo dati. Intent può assumere solo i valori in, out, inout o anche, equivalentemente, in out). Il significato dell' intent(in) è  che i corrispondenti parametri formali vanno intesi come "read-only", solo in entrata, non modifcabili all' interno del sottoprogramma. Un' eventuale assegnazione verrebbe segnalata come errore dal compilatore. Intent(out) informa il compilatore che il corrispondente parametro permette di trasmettere un valore calcolato nel sottoprogramma al programma chiamante. Infine intent(inout)  (il default), permette di modificare il valore nel programma chiamante di una variabile usata come argomento del sottoprogramma.

Esempi di dichiarazione di sottoprogrammi:

Subroutine:

subroutine aaa(x,y,z)
implicit none
real, intent(in)   :: x,y
real, intent(out) :: z

z = x + 2*y

end subroutine aaa

Function:

function fun(x,y) result(r)
implicit none
real, intent(in)  :: x,y
real              :: r

r = x**2+y**2

end function fun
Da notare l' assenza di intent per il risultato della function r:  è  una variabile locale alla function e non un argomento.
Le variabili locali, quelle cioè  che non figurano tra gli argomenti, ma dichiarate nel sottoprogramma, sono visibili ed accessibili solo all' interno del sottoprogramma. Pertanto possono avere lo stesso nome di variabili del programma chiamante senza che vi sia alcuna interferenza.
Se tra gli argomenti con intent in oppure inout appare una stringa (variabile di carattere di lunghezza data) la dichiarazione del parametro formale dovrà  essere:

character(len=*),intent(in)      ::  stringa

oppure

character(len=*),intent(inout)   ::  stringa

La lunghezza della variabile stringa non è  indicata esplicitamente (anche perché  difficilmente nota in anticipo) ma sarà  uguale a quella dell' espressione di tipo character con cui il sottoprogramma sarà chiamato. All'interno del sottoprogramma, l' utilizzo della funzione di libreria len permetterà  di accedere alla lunghezza dell' argomento corrispondente: len(stringa).
Infine, se tra gli argomenti appare un array la dichiarazione del parametro formale corrispondente può assumere diverse forme.
Nel seguito ci limiteremo alla sintassi corrispondente ai cosiddetti  assumed-shape arrays  array di forma presunta. Il loro utilizzo è  però possibile solo nei programmi che dispongono dell' interfaccia esplicita del sottoprogramma  (vedi dopo), per esempio l'iterfaccia esplicita esiste in tutti i sottoprogrammi di modulo..
La sintassi corrisponde ad indicare nell' attributo dimension solo il numero di indici (o al più  il loro estremo inferiore) con il carattere "due punti" (:). Es:

integer,dimension(:,:),intent(in)      :: a
complex,dimension(:),intent(out)    :: z
real,dimension(0:,-5:),intent(inout)  ::  w, p

L' estensione degli array di forma presunta si ricava attraverso la function size. size(a,i)  fornisce il numero di componenti dell' array a  relativi all' i-esimo indice. P.es., in uma matrice, size(a,2)  ritornerà  il numero di colonne della matrice (il secondo indice è  quello di colonna).


Nella chiamata ad una procedura gli argomenti con intent in possono essere costituiti da espressioni, ovvero da


Moduli


Un modulo  è assimilabile ad un contenitore software che può avere al suo interno: costanti simboliche, variabili globali, definizioni di nuovi tipi dati e sottoprogrammi. Il contenuto di un modulo diviene accessibile alle sole unità  di programma che usano quel modulo.

Le ragioni per preferire l'  uso dei sottoprogrammi definiti all'  interno dei moduli sono diverse:
  1. i moduli comportano la costruzione dell'  interfaccia da parte del compilatore,  semplificando il compito del programmatore;
  2. i moduli permettono un migliore controllo sull'  accessibilità,   eventualmente parziale,  di più  sottoprogrammi tra loro collegati,  migliorando  la modularità  del codice;
  3. i  moduli permettono di  emulare gran parte delle funzionalità  delle classi del paradigma di programmazione ad oggetti,  in cui l'  accento viene spostato dai sottoprogrammi ai dati  su cui questi agiscono. In effetti  i moduli permettono di relizzare un legame stretto, a livello del codice, tra dati e sottoprogrammi ad essi collegati.

Schematicamente un modulo è  costituito da un blocco di istruzioni comprese tra le due istruzioni
module nome_modulo
...
end module nome_modulo
Dove nome_modulo è  un qualsiasi nome valido Fortran (stringa di max 31 caratteri, solo alfanumerici e  underscore ("_"), con l'  obbligo di non iniziare  con una cifra.

Nel modulo ci possono essere una o due sezioni. Se  c'è  la seconda, il suo inizio viene segnalato  dalla parole CONTAINS.
Nella prima sezione vanno obbligatoriamente le dichiarazioni di variabili e  costanti del modulo,  i tipi dati derivati  e le eventuali dichiarazioni di carattere pubblico o privato delle entità  definite nel modulo o la richiesta che i dati siano conservati tra un utilizzo ed un altro del modulo stesso. Tutto ciò  che viene dichiarato privato ( PRIVATE ) nel modulo non è  visibile nelle procedure che utilizzano il modulo.
L' istruzione
PRIVATE
rende "private" l' attributo di default di ogni entita' del modulo.

E' consigliabile iniziare ogni modulo con l' istruzione
SAVE
in modo che il valore dei campi dati eventualmente definiti nel modulo siano preservati anche nel caso di chiamate a sottoprogrammi che non utilizzino il modulo.
Per utilizzare un modulo,  ciascun sottoprogramma che ne ha bisogno,  dovrà  avere un'  istruzione

USE nome_modulo

Come prima istruzione subito dopo il nome del sottoprogramma stesso.
L'  istruzione USE  rende dunque visibili nella procedura che utilizza il modulo tuti i nomi pubblici dello stesso. Eventualmente si può  utilizzare solo alcune  delle entità  del modulo attraverso il meccanismo del parametro ONLY  nell'  istruzione USE. Ad esempio, con
USE nome_modulo, ONLY:n1,n2,n3
si rendono visibili nel programma che usa il modulo solo le entità (variabili, tipi dati, sottoprogrammi) di nome n1,n2,n3, indipendentemente dall' esistenza di altre entità pubbliche.

E'  anche possibile che all'  interno del programma che utilizza il modulo, alcune entità  del modulo siano ribattezzate con un nome diverso. Per esempio, per usare un modulo  e far sì  che la variabile del modulo di nome var_mod  sia indicata nel programma come var_prog, basta  usare l'  istruzione nel seguente modo:

USE nome_modulo, var_prog => var_mod
Un modulo può usare un altro modulo (ma non è consentita ricorsione).

Procedure esterne

L' uso di procedure non presenti all' interno di un modulo è potenzialmente pericolosa (non vengono effettuati controlli sulla corrispondenza tra numero e tipo degli argomenti tra programma chiamante e chiamato. Nel caso non sia possible farne a meno, può essere utile indicare nel programma chiamante questa situazione mediante la dichiarazione EXTERNAL :: nome_procedura nel caso di function che ritorni un tipo dati, il nome della function va anche dichiarato del tipo dati in questione. questione. P.es. se abbiamo una function che nome FX e calcola un valore real con kind rk la dichiarazione dovrà essere REAL(kind=rk), EXTERNAL :: FX