Fortran 77 wird abgelöst; (Teil 2)

Fortran 90: Neuer Entwurf zur Diskussion freigegeben

21.09.1990

Die im wissenschaftlich-technischen Bereich eingesetzte Programmiersprache Fortran hat eine Frischzellenkur erhalten, Karl-Heinz Rotthäuser* zeichnet in seinem dreiteiligen Beitrag nach, welche Veränderungen "Fortran 90" im Vergleich zu "Fortran 77" bringen wird. Gegen den bei der ISO erschienenen Entwurf "Draft International Standard Fortran"** kann noch bis Februar 1991 Einspruch erhoben werden.

Maskierte Matrixzuweisungen (Masked Array Assignment = WHERE) können entweder eine Zuweisung oder ein Konstrukt enthalten. Ihre Ausführung erfolgt durch die WHERE-Anweisung.

Beispiel.

REAL, DIMENSION(1:10, 1:20) TEMP, DIFF-TEMP, DRUCK, DIFF_DRUCK

...

WHERE (TEMP< 100.0) TEMP = TEMP - DIFF_TEMP

In diesem Beispiel handelt es sich um eine Zuweisung. Bei der Ausführung wird der boolesche Ausdruck in der Klammer ausgewertet, und für alle Elemente, für die dieser Ausdruck den Wert .TRUE. annimmt, wird den entsprechenden Elementen der Matrix TEMP das Ergebnis zugewiesen. Das WHERE-Konstrukt darf ebenfalls nur Zuweisungen enthalten.

Beispiel:

WHERE (TEMP > 50.0)

DRUCK = DRUCK + DIFF_DRUCK

TEMP = TEMP - DIFF_TEMP

ELSEWHERE

DRUCK = 1.0

END WHERE

In diesem Beispiel wird zuerst der boolesche Klammerausdruck ausgewertet. Für alle Elemente, für die der Ausdruck den Wert .TRUE. annimmt, werden die Zuweisungen elementweise im WHERE-Block ausgeführt. Für die übrigen Werte (.FALSE.) werden die Zuweisungen des ELSEWHERE-Blockes ausgeführt.

Die Arithmetik ist genauer geworden

Der Datentyp der reellen Zahlen nimmt Werte an, um die reellen Zahlen aus der Mathematik zu approximieren. Ein Prozessor muß mindestens zwei Approximationsmethoden liefern, um die Menge der Daten vom Typ reell zu definieren. Die Methode wird durch den KIND-Parameter ausgewählt. Den richtigen KIND-Wert für eine ausgewählte Genauigkeit und einen Exponentenbereich liefert die innere Funktion SELECTED-REAL-KIND.

Bei der Deklaration von REAL- und COMPLEX-Größen können diese KIND-Parameter angegeben werden:

REAL(KIND = SELECTED _REAL-KIND(8,70)) X(100)

In diesem Beispiel besitzt jedes Element der Matrix X eine Genauigkeit von wenigstens 8 Dezimalziffern und einen Exponentenbereich, der von 10 (70) bis 10 (30) reicht. Der Kompilierer benutzt diese Angaben, um die Variable auf die am besten geeignete interne Rechenvariable - in der Regel eine Gleitkommavariable - abzubilden, die diese Anforderungen befriedigt.

Durch diese Methode wird es möglich, Fortran-90-Programme portabler zu gestalten, was insbesondere für Bibliotheken mit arithmetischen Unterprogrammen relevant ist. Um eine Konstante mit einer bestimmten dezimalen Genauigkeit und einem vorgegebenen Exponentenbereich zu vereinbaren, muß der KIND-Selektor angegeben werden. Das geschieht durch Anhängen der Zeichen Unterstrich und Selektor an die Konstante, zum Beispiel:

...

CALL SUB (10.954785E4_8)

...

SUBROUTINE SUB (DUMMY)

REAL (KIND = 8) DUMMY

...

In diesem Fall dient der Zusatz "_8" dazu, die Genauigkeit und den Exponentenbereich für die an das Unterprogramm übergebene numerische Konstante vom Typ REAL zu spezifizieren.

Als zusätzlicher Datentyp wurden Datenstrukturen (Derived Types) eingeführt. Datenstrukturen werden in TYPE-Blöcken definiert; dazu ist die Bezeichnung der Datenstruktur sowie Name und Typ seiner Komponenten anzugeben.

Beispiel:

TYPE PERSON

INTEGER ALTER

CHARACTER (LEN = 50)

NAME

END TYPE PERSON

In diesem Beispiel wird eine Datenstruktur mit dem Namen PERSON definiert, die die beiden Komponenten ALTER und NAME enthält. Es ist erlaubt, Datenstrukturen, die vorher definiert wurden, innerhalb von Datenstrukturen zu verwenden. Wenn eine Datenstruktur auch in einer COMMON-Anweisung verwendet werden soll, muß die Datenstruktur-Definition die Anweisung SEQUENCE enthalten, wodurch die Speicherfolge der Komponenten festgelegt wird:

TYPE WAHL

SEQUENCE

INTEGER ALTER

LOGICAL WAHLBERECHTIGT

ENDTYPE WAHL

Die Deklaration (Einrichtung) einer Struktur erfolgt mit der Typanweisung, wobei als Objekt auch eine Matrix einer Struktur gebildet werden darf:

TYPE (PERSON):: PERSONA TYPE (PERSON), DIMENSION(1:10):: PERSONB

Die Beispiele beziehen sich auf die vorhergehenden Definitionen: PERSONA ist ein Skalar vom Typ PERSON, PERSONB eine Matrix vom Typ PERSON.

Eine Strukturdefinition enthält eine oder mehrere Komponenten. Der Zugriff auf die Komponenten erfolgt mit dem Prozentzeichen (%), zum Beispiel:

PERSONA % ALTER

PERSONB (J) % ALTER

Durch eine Folge von Werten, das heißt einen Wert je Komponente, erzeugt man einen Strukturwert. Wenn alle Komponenten konstante Ausdrücke sind, ergibt sich eine Strukturkonstante. Das folgende Beispiel benutzt die Definitionen aus diesem Kapitel:

PERSON (21, "AXEL SCHMITT")

Hier enthält die Strukturkonstante eine Zeichenkonstante ohne KIND-Selektor, da es sich um einen Zeichentyp für den Fortran-Zeichensatz handelt.

Von den internen Operationen ist nur die Zuweisung anwendbar. Es lassen sich jedoch weitere Operationen mit eigenen Operatoren definieren. Folgende Prozeduren werden unterschieden: Externe Prozeduren, Modulprozeduren und interne Prozeduren. Sie zerfallen in zwei prinzipiell unterschiedliche Prozedurarten: Funktionen und Subroutinen. Daneben gibt es noch den sogenannten Prozedur-Interface-Block.

- Eine externe Prozedur ist ein externes Unterprogramm, das sich sowohl mit Fortran 90 als auch auf anderen Wegen erzeugen läßt.

- Eine Modulprozedur wird durch ein Unterprogramm in einem Modul definiert. Jedes Unterprogramm desselben Moduls und jede Programmeinheit, die das Modul benutzt, kann sie aufrufen.

- Eine interne Prozedur wird von einem internen Unterprogramm definiert. Ein internes Unterprogramm kann zu einem Hauptprogramm oder zu einem Unterprogramm gehören. Es verhält sich "lokal" zu diesem Programm in dem Sinne, daß auf das interne Unterprogramm nur innerhalb dieses Programms und all seiner internen Prozeduren zugegriffen werden darf. Außerhalb dieses Bereiches kann nicht auf das interne Unterprogramm zugegriffen werden.

- In einem Prozedur-Interface-Block werden die Prozedurparameter einer externen Prozedur beschrieben; damit läßt sich der Prozeduraufruf kontrollieren. Für diesen Zweck enthält der Block die Charakteristika der Prozedur: den Namen mit den dazugehörigen Attributen für jeden formalen Parameter. Man kann mit dem Prozedur-Interface-Block aber auch einen genetischen Namen und einen Operator definieren, mit dem sich eine Prozedur ebenfalls aufrufen läßt. Innerhalb eines Gültigkeitsbereiches darf es für eine Prozedur nur ein einziges Interface geben.

Beispiele:

Modul-Unterprogramm:

MODULE VORZEICHEN

CONTAINS

INTEGER FUNCTION

SIGNUM(I)

IF (I< 0 ) THEN

SIGNUM= -1

ELSE IF (I = = 0) THEN

SIGNUM = 0

ELSE

SIGNUM = +1

END IF

END FUNCTION SIGNUM

END MODULE

Interne Prozedur:

SUBROUTINE EXTERN

REAL A, B, C,

...

CALL LOESCHEN

...

CONTAINS

SUBROUTINE LOESCHEN

REAL X

A= 0.0

B= 0.0

C= 0.0

...

END SUBROUTINE LOESCHEN

END SUBROUTINE EXTERN

In diesem Beispiel enthält die externe Subroutine EXTERN die interne Subroutine LOESCHEN, die die Variablen A, B, C des Unterprogramms EXTERN löscht. Die internen Prozeduren werden mit CONTAINS eingeleitet und stehen am Ende der externen, der Modul-Prozeduren oder des Hauptprogramms.

Prozedur-Interface-Block:

SUBROUTINE INVERSE

(A, FN)

REAL A

INTERFACE

FUNCTION FN (B)

REAL FN, B

END INTERFACE

...

END SUBROUTINE

In diesem Beispiel spezifiziert der Interface-Block, daß das zweite Argument der Subroutine eine reelle Funktion mit einem reellen Argument ist.

Funktionen und Unterprogramme in Fortran 90 dürfen künftig auch rekursiv sein, wenn sie das Schlüsselwort RECURSIVE in ihrem Prozedurkopf tragen. Dieses Schlüsselwort ist immer dann anzugeben, wenn sich die Prozedur direkt oder indirekt selbst aufruft.

Beispiel:

RECURSIVE INTEGER FUNCTION BEST (A, B) RESULT (ERG)

INTEGER A, B

...

ERG = BEST (A_1, B_1)

END FUNCTION BEST

Mit dem Schlüsselwort RESULT wird die Ergebnisvariable der Funktion spezifiziert - im obigen Beispiel: ERG. Jedes Auftreten des Funktionsnamens im Ausführungsteil "der Funktion ist ein rekursiver Funktionsaufruf. Wenn RESULT nicht angegeben wird, ist die Ergebnisvariable identisch mit dem Funktionsnamen und jedes Auftreten des Funktionsnamens im Ausführungsteil bezieht sich auf die Ergebnisvariable und ist kein rekursiver Funktionsaufruf.

Für eine Funktion kann der Anwender auch einen Operator definieren. Dadurch wird es möglich, die Funktion auch mit diesem vereinbarten Operator aufzurufen; dies gilt jedoch nur für ein und zwei Argumente. Der Operator wird durch die OPERATOR-Option vereinbart, wobei er folgende Gestalt annehmen darf, Buchstabenfolge" zum Beispiel ".ENTHAELT.". Jede Referenz mit dem vereinbarten Operator, bei der die Operanden dieselben Charakteristika wie die formalen Funktionsparameter aufweisen, wird als Funktionsaufruf behandelt.

Dadurch kann ein Modul sämtliche Strukturdefinitionen und alle Prozeduren für die Definition der Operationen mit diesen Datentypen enthalten. Auf diese Art und Weise unterstützt die Modultechnik die Datenabstraktion und stellt einen Beitrag zur Modernisierung von Fortran 90 dar.

Anstelle eines neuen Operators kann man auch einem internen Operatorsymbol eine zusätzliche Bedeutung geben, indem man dieses Operatorsymbol in der OPERATOR-Option eines INTERFACE-Blockes für weitere Datentypen definiert.

Beispiel:

TYPE STRING

INTEGER LAENGE

CHARACTER (LEN = 200)

TEXT

ENDTYPE STRING

...

INTERFACE OPERATOR(//)

TYPE (STRING) FUNCTION VERKETTEN(S1,S2)

TYPE (STRING) :: S1,S2

END INTERFACE

...

In dem Beispiel wird eine Funktion für die Datenstruktur STRING definiert. Sie verkettet zwei Objekte vom Typ STRING durch Aufruf der Funktion VERKETTEN oder durch die Benutzung des Operators //:

VERKETTEN (STR1,STR2) oder STR1 // STR2

Dadurch wird der interne Operator //, der für Daten vom Typ CHARACTER definiert ist, auch für die Datenstruktur STRING definiert.

Eine Subroutine kann man mit dem Schlüsselwort ASSIGNMENT im INTERFACE-Block auch als Zuweisung spezifizieren; diese Subroutine darf dann auch in Form einer Zuweisung aufgerufen werden. Sie muß in diesem Fall aber genau zwei Argumente haben, die den beiden Seiten einer Zuweisung entsprechen.

Beispiel:

INTERFACE ASSIGNMENT

SUBROUTINE ASSIGN_ZEICHENKETTE (STR, CHAR)

TYPE (STRING):: STR

CHARACTER(LEN = *)

:: CHAR

END SUBROUTINE ASSIGN_ZEICHENKETTE

END INTERFACE

SUBROUTINE ASSIGN-ZEICHENKETTE (STR, CHAR)

TYPE (STRING):: STR

CHARACTER (LEN = *)

:: CHAR

STR % LAENGE = LEN (CHAR)

STR % TEXT = CHAR

END SUBROUTINE ASSIGN_ZEICHENKETTE

In dem Beispiel wird auf die obige Definition der Struktur STRING zurückgegriffen. LEN ist eine innere Funktion, die die Länge der Zeichenkette liefert. Durch diese Definition der Subroutine ist es möglich, einer Struktur vom Typ STRING (zum Beispiel STRING 1) einen Ausdruck vom Typ CHARACTER mit einer Zuweisung zuzuweisen:

STR1 = "Anton"

Auf zwei oder mehrere Prozeduren darf mit demselben Namen zugegriffen werden. Die Unterscheidung, welche Prozedur tatsächlich aufgerufen wird, muß sich eindeutig aus der Argumentliste ergeben (bei den inneren Funktionen kennt man dieses Prinzip der genetischen Namen schon länger). Der generische Name wird in einem speziellen INTERFACE-Block festgelegt. Wir sahen bereits, daß man in ähnlicher Weise mit einem Operationszeichen auf mehrere Funktionen zugreifen konnte und mit dem Gleichheitszeichen (=) mehrere Subroutinen aufrufen konnte.

Bei formalen Parametern kann die beabsichtigte Benutzung mit dem INTENT-Attribut angegeben werden, und zwar IN, OUT oder INOUT:

- INTENT(IN) weist einen formalen Parameter als Eingabeparameter aus.

- INTENT(OUT) kennzeichnet einen formalen Parameter als Ausgabeparameter.

- Mit INTENT(INOUT) spezifizierte Parameter sind sowohl als Eingabe- als auch als Ausgabeparameter verwendbar:

SUBROUTINE TRANSFER (VON, NACH)

REAL, INTENT (IN):: VON REAL, INTENT (OUT) :: NACH

...

Darüber hinaus dürfen formale Parameter das Attribut OPTIONAL haben. Solche Parameter erfordern beim Aufruf kein aktuelles Argument.

Beim Prozeduraufruf dürfen die Namen der formalen Parameter wiederholt werden: CALL ASSIGN_ZEICHENKETTE(STR = STRING 1, CHAR ="Anton")

Vorteil dieser Schreibweise: Die Reihenfolge der aktuellen Parameter ist unabhängig von der Reihenfolge der formalen Parameter, und optionale Parameter dürfen einfach weggelassen werden. (wird fortgesetzt) *