Fortran: tipi derivati


Oltre ai tipi primitivi,  il Fortran,  come la maggior parte dei linguaggi moderni di alto livello, permette la definizione da parte del programmatore di nuovi tipi dati per aggregazione di quelli primitivi. Ad esempio,  un tipo dati in grado di rappresentare un elemento chimico potrebbe essere definito come:
TYPE :: elemento
CHARACTER(LEN=2) :: simbolo
INTEGER :: numero_atomico
REAL :: massa_atomica
END TYPE elemento
I nomi simbolo, numero_atomico e massa_atomica rappresentano i nomi dei tre campi dati di cui e'  costituito il tipo dati "elemento".
Una volta definito il nuovo tipo dati, si possono dichiarare variabili del nuovo tipo  mediante dichiarazioni come:
TYPE(elemento) :: atomo1, atomo2
TYPE(elemento),DIMENSION(92) :: tavola_periodica


Il nuovo tipo dati può essere utilizzato come tipo di un campo di un ulteriore tipo dati derivato:
type :: atom
  type(elemento) :: elemento
  real, dimension(3):: pos
end type atomo

Da notare che le uniche operazioni predefinite su entità del nuovo tipo dati sono:
  1. l' assegnazione :
     atomo1 = atomo2
    assegna tutti i campi della variabile atomo2  ai rispettivi campi di atomo1)
  2. l' input/output    (es.
    print*, atomo1 
    read*, atomo2
    rispettivamente scrivono o leggono tutti i campi delle corrispondenti variabili,  nell' ordine con cui sono dichiarate nel tipo dati.
  3. si puo' "costruire" una variabile del nuovo tipo dati a partire dai valori dei corrispondenti campi mediante una istruzione "costruttore" con lo stesso nome del tipo:
    atomo1 = element("H",1,1.0079)
    I costruttori possono essere utilizzati anche nelle assegnazioni fatte contestualmente alla dichiarazione di una variabile. Va tuttavia notato che i costruttori predefiniti nel linguaggio pongono delle limitazioni al programmatore: vanno modificati nel caso di ridefinizione del tipo dati e cessano di funzionare in presenza di campi private nella definizione del tipo dati. Per tale motivo si preferisce spesso sostituirli con function "costruttrici" scritte dal programmatore.
Se un tipo derivato contiene una o più  componenti allocabili queste vanno allocate esplicitamente. Tuttavia se una variabile del tipo derivato è  a sinistra del segno di uguale in un' assegnazione dove a destra figura un' altra espressione dello stesso tipo derivato, l' allocazione ( o riallocazione) avviene automaticamente in modo che le componenti allocabili a sinistra e destra del segno di uguale  abbiano lo stesso numero di componenti.


Qualsiasi altra operazione deve essere definita mediante sottoprogrammi appositamente scritti dal programmatore.
 
I singoli campi di una variabile di un tipo dati derivato sono accessibili mediante il nome della variabile seguito dal selettore (%) e dal nome del campo (come risulta dalla definizione del tipo dati).  
P.es.
atomo1%simbolo = 'Be'
atomo1%numero_atomico = 4
atomo1%massa_atomica = 9.01218
atom2 = atom1
 print*, atomo2%massa_atomica
 
E' buona pratica di programmazione inserire in un unico modulo la definizione di ciascun tipo dati indipendente insieme a tutti i sottoprogrammi necessari a manipolarlo.

E'  anche possibie definire nuovi operatori binari (due operandi, uno a sinistra e uno a destra del simbolo del nuovo operatore) o unari (un solo operando a destra del simbolo del nuovo operaore).  O anche,  mediante interfacce esplicite (da inserire nella sezione dati di un modulo) e'  possibile  aggiungere (sovraccaricare, overloading)   nuove funzionalita'  ad operatori preesistenti o  alla assegnazione o a sottoprogrammi di libreria.

Esempi:

Estensione della  funzione intrinseca SIN(x)  ad argomenti interi:
module newtyp
implicit none
interface sin ! aggiunge funzionalta' alla funzione di libreria sin(x)
module procedure sin_i
end interface sin
contains
function sin_i(i) result(s)
real :: s
integer,intent(in) :: i
s=sin(real(i))
end function sin_i
end module newtyp

program p
use newtyp
implicit none
print*,sin(1.0),sin(1)
end program p

Definizione di un operatore binario .X.  per il prodotto vettoriale tra vettori a tre componenti:
module tiponuovo
implicit none
private ! rende non accessibili tutte le entita' del modulo che
! non siano esplicitamente dichiarate public
interface operator (.X.)
module procedure v_prod
end interface
public :: operator(.X.) ! in tal modo i programmi che utiizzano il modulo
! possono usare solo l' operatore .X. ma non
! la corrispondente procedura v_prod
contains
function v_prod(a,b) result (p)
real,dimension(3),intent(in) :: a,b
real,dimension(3) :: p
p(1)=a(2)*b(3)-a(3)*b(2)
p(2)=a(3)*b(1)-a(1)*b(3)
p(3)=a(1)*b(2)-a(2)*b(1)
end function v_prod
end module tiponuovo

program pvec
use tiponuovo
implicit none
real,dimension(3) :: a,b
read*,a
read*,b
print*,a.X.b
end program pvec

Ridefinizione (overloading)  dell' operatore somma per un nuovo tipo dati (Punto2D  che rappresenta punti del piano mediante due campi reali):

module pv
implicit none
private
type, public :: punto2D
real ::x
real ::y
end type punto2D
interface operator(+)
module procedure add_p2D
end interface
public :: operator(+)
contains
function add_p2D(p,q) result(r)
type(punto2D),intent (in) :: p,q
type(punto2D) :: r
r%x = p%x + q%x
r%y = p%y + q%y
end function add_p2D
end module pv

program usa_pv
use pv
implicit none
real :: x
type(punto2D) :: p,q
p=punto2D(12.0,-3.0)
q=punto2D(2.0,5.0)
print*," p = (12,-3) q = (2,5) "
print*,"p+q = ",p+q
end program usa_pv


Procedure legate al tipo dati (Type-bound procedures)

Soprattutto nella programmazione che utilizza il paradigma ad oggetti, si preferisce legare in modo stretto le procedure che agiscono su variabili del nuovo tipo dati alla variabile (istanza dell' oggetto) stessa. Il Fortran permette di legare più procedure (metodi) ad un tipo dati nel modo illustrato dal seguente esempio:

module class_Circle
  implicit none
  private
  real :: pi = acos(-1.0)

  type, public :: Circle
     real :: radius
   contains
     procedure :: area => circle_area
  end type Circle
contains
  function circle_area(this) result(area)
    class(Circle), intent(in) :: this
    real :: area
    area = pi * this%radius**2
  end function circle_area
end module class_Circle


program circle_test
  use class_Circle
  implicit none

  type(Circle) :: c     ! Declare a variable of type Circle.
  c = Circle(1.5)       ! Use the implicit constructor, radius = 1.5.
  print*,c%area()       ! Call the type-bound subroutine
end program circle_test
Da notare:
  1. Nella function circle_area l' argomento di tipo dati Circle è dichiarato come class(Circle) e non type(Circle). A questo livello la si può considerare come una richiesta della sintassi;

  2. la function circle_area ha un argomento (this), il primo se ce ne è più di uno, che fa riferimento alla variabile del tipo Circle a cui il metodo area è associato;

  3. in questo caso in cui l' unico argomento è this, nella chiamata non viene indicato nessun argomento. In generale il primo viene omesso (è implicito).