Fortran im Wandel der Zeit

Seite 5: Objektorientierung

Inhaltsverzeichnis

Beginnend mit dem Standard Fortran 90 bis zum aktuellen (Fortran 2008) hielten nach und nach Elemente der objektorientierten Programmierung Einzug in die Sprache. Um zu zeigen, was durch sie aktuell möglich ist, folgt die Beschreibung des Implementierens und Verwendens einer Klasse. In der Objektorientierung vereint eine Klasse Daten (Attribute) und Operationen (Methoden). In Fortran lässt sich das durch einen sogenannten Derived Type in einem Modul mit Funktionen und Subroutinen realisieren, die hinter einer contains-Anweisung stehen.

Weitere objektorientierte Konzepte können Entwickler umsetzen, wenn Zugriffsmodifikatoren zur Datenkapselung sowie Überladen von Funktionen und Operatoren dazu kommen. Listing 12 zeigt als Beispiel ein Modul mit der grundlegenden Struktur einer Klasse, mit der sich Brüche, also rationale Zahlen mit ganzzahligem Zähler und Nenner, darstellen und verrechnen lassen:

1   module RationalClass
2 implicit none
3
4 type, public :: Rational
5 private
6 integer num, den
7 end type Rational
8
9 interface Rational
10 module procedure construct
11 module procedure constructInteger
12 end interface
13
14 interface assignment(=)
15 module procedure assignInteger
16 end interface
17
18 interface operator(*)
19 module procedure times
20 module procedure timesRational
21 module procedure timesInteger
22 end interface
21
22 interface abs
23 module procedure absRational
24 end interface
25
26 interface writeRational
27 module procedure writeRationalScalar
28 end interface
29
30 contains
31
32 ! constructors:
33 elemental type(Rational) function construct(num, den)
34 integer, intent(in) :: num
35 integer, intent(in) :: den
36 construct%num = num
37 construct%den = den
38 end function
39
40 elemental type(Rational) function constructInteger(num)
41 integer, intent(in) :: num
42 constructInteger = construct(num, 1)
43 end function
44
45 ! getters:
46 elemental real(8) function getnum(r)
47 type(Rational), intent(in) :: r
48 getnum = r%num
49 end function
50
51 elemental real(8) function getden(r)
52 type(Rational), intent(in) :: r
53 getden = r%den
54 end function
55
56 ! setters:
57 elemental subroutine setnum(r,num)
58 type(Rational), intent(inout) :: r
59 integer, intent(in) :: num
60 r%num = num
61 end subroutine
62
63 elemental subroutine setden(r,den)
64 type(Rational), intent(inout) :: r
65 integer, intent(in) :: den
66 r%den = den
67 end subroutine
68
69 ! assignment operator:
70 elemental subroutine assignInteger(r, num)
71 type(Rational), intent(inout) :: r
72 integer, intent(in) :: num
73 r%num = num
74 r%den = 1
75 end subroutine
76
77 ! multiplication operators:
78 elemental type(Rational) function times(r1, r2)
79 type(Rational), intent(in) :: r1, r2
80 times%num = r1%num * r2%num
81 times%den = r1%den * r2%den
82 end function
83
84 elemental type(Rational) function timesRational(r, i)
85 type(Rational), intent(in) :: r
86 integer, intent(in) :: i
87 timesRational = times(r, Rational(i))
88 end function
89
90 elemental type(Rational) function timesInteger(i, r)
91 type(Rational), intent(in) :: r
92 integer, intent(in) :: i
93 timesInteger = times(Rational(i), r)
94 end function
95
96 ! abs function:
97 type(Rational) function absRational(r)
98 type(Rational), intent(in) :: r
99 absRational = Rational(abs(r%num),abs(r%den))
100 end function
101
102 ! output:
103 subroutine writeRationalScalar(r)
104 type(Rational), intent(in) :: r
105 write(*,*) 'Rational object: ', r%num, '/', r%den
106 end subroutine
107
108 end module RationalClass

Listing 12: Definition einer Klasse

Listing 13 enthält ein einfaches Hauptprogramm dazu. In Listing 12 erkennt man die Deklaration eines selbstdefinerten Datentyps in den Zeilen 4-7. Dort kommen die Zugriffsmodifikatoren public für den Typ selbst und private für die beiden Attribute num (für Numerator, Zähler) und den (für Denominator, Nenner) des Bruches zum Einsatz. Die Definition des Typs Rational erlaubt es nun, im Hauptprogramm (Listing 13, Zeile 5) Variablen dieses Typs anzulegen:

1  program RationalTest
2 use RationalClass
3 implicit none
4
5 type(Rational) :: r1, r2
6 type(Rational), allocatable :: r3(:)
7 integer :: i, n = 3
8
9 r1 = Rational(1, 2)
10 r2 = Rational(3)
11 call writeRational(r1)
12 call writeRational(r2)
13 r2 = r1*r2
14 call writeRational(r2)
15
16 allocate(r3(n))
17 r3 = Rational(0)
18 r3 = r3 * r3
19 do i=1,n
20 call writeRational(r3(i))
21 enddo
22 deallocate(r3)
23
24 end program

Listing 13: Benutzung der Klasse

Hinter der Typdefinition folgen im Modul nun diverse Interface-Blöcke. Im ersten (Zeilen 9-12) findet die Definition des Interface Rational mit zwei im Modul definierten Prozeduren statt. Der Begriff procedure steht an der Stelle für Fortran-Funktionen oder Subroutinen.

Ruft das Hauptprogramm (Listing 13, Zeilen 9, 10) Rational auf, wird in den im Interface-Block aufgeführten Modulprozeduren diejenige gesucht, die mit ihren Parametern passt. Im vorliegenden Fall ruft Zeile 9 des Hauptprogramms so über das Interface Rational die Funktion construct aus Zeile 35 des Moduls auf, die zwei Integer-Parameter bekommt. Zeile 10 des Hauptprogramms ruft dagegen diejenige mit nur einem Parameter auf (Zeile 42 im Modul). Auf völlig identische Weise lassen sich nun Funktionen und Operatoren, wie hier der Multiplikationsoperator, überladen. Bei den Prozeduren im Modul wurden zusätzlich die jeweiligen Parameter als intent(in) beziehungsweise out und inout gekennzeichnet. Der Compiler prüft diese in Fortran 90 eingeführte Spezifizierung, die einen zusätzlichen Schutzmechanismus darstellt.

Im Hauptprogramm lässt sich erkennen, dass es direkt ein Feld (Variable r3) des neuen Datentyps erzeugen, initialisieren und mit ihm rechnen konnte. Das kann funktionieren, wenn die entsprechenden Prozeduren wie im Modul als elemental gekennzeichnet sind. Das ermöglicht eigenen Datentypen und Klassen eine komfortable Syntax, die der für Felder primitiver Datentypen entspricht.

Auch Vererbung ist in Fortran möglich. Da man solche modernen Konstrukte in Legacy-Code eher nicht findet und auch die Bedeutung von Vererbung in numerischen Simulationscodes umstritten ist, wird hier allerdings nicht näher darauf eingegangen.

Eine sehr kompakte, aber dennoch umfassende Darstellung des modernen Fortran ist in [4] zu finden – nicht nur im Hinblick auf Klimamodelle, wie der Titel andeuten könnte. Daneben sei Interessierten das Buch "Modern Fortran Explained"[6] empfohlen, das weniger Lehrbuch als Nachschlagewerk zum jeweils aktuellen Sprachstandard ist. Die Autoren sind an dessen Gestaltung beteiligt. In "Computing for Scientists" [7] wird ein interessanter Vergleich zwischen Fortran 90 und C++ im Hinblick auf Objektorientierung gezogen, die beim Erscheinen des Buches in Fortran aber noch nicht vollständig realisiert wurde.