Jag vill även upprepa att under Fortran 77 är alla programenheter i stort sett på samma nivå, även om huvudprogrammet logiskt sett verkar vara överordnat de subrutiner och funktioner som anropas, och att man till och med kan rita upp en anropsgraf som ser ut som ett träd. I verkligheten ligger eventuella BLOCK DATA på en högre nivå, och alla andra programenheter ligger på samma nivå, ur Fortransystemets synpunkt, dock med huvudprogrammet ett litet snäpp högre. Ett undantag är de så kallade satsfunktionerna, vars definitioner kan ligga först i en programenhet (direkt efter deklarationerna), och som är interna för den enheten, och således befinner sig på en logiskt sett lägre nivå. Den normale Fortranprogrammeraren använder dock tyvärr aldrig sådana funktioner.
Ovanstående innebär att alla rutinnamn är på samma logiska nivå, vilket innebär att två olika rutiner i helt olika delar av ett stort program inte får ha samma namn. Ofta innehåller numeriska och grafiska bibliotek tusentals rutiner (med högst sex tecken i namnen under gamla Fortran), varför det är en påtaglig risk för namnkonflikt. Detta var en sak som de gamla satsfunktionerna kunde avhjälpa, eftersom de är interna för respektive enhet, och därför kan finnas under samma namn men med olika uppgift i olika programenheter. Nackdelen är att de bara kan hantera vad som ryms på en programrad (men de kan anropa varandra på så sätt att en senare kan anropa en tidigare, men ej tvärtom).
Nu har interna funktioner och subrutiner tillkommet. Dessa definieras sist i respektive programenhet (dock ej i BLOCK DATA) efter det nya kommandot CONTAINS och före END. Ett internt subprogram har tillgång till samma variabler som enheten den tillhör, inklusive möjligheten att anropa dess andra interna subprogram. Det skrives i övrigt som ett vanligt subprogram, men får ej kapslas, dvs ej ha egna interna funktioner eller subrutiner.
Vanliga subrutiner och funktioner kallas liksom tidigare externa subrutiner och externa funktioner, men nu finns det en större anledning till den benämningen än tidigare. Numera finns ju även interna subprogram, tidigare fanns bara inbyggda (intrinsic) som alternativ. För övrigt har antalet inbyggda funktioner ökat mycket kraftigt.
I variabeldeklarationer till subprogram har för argumenten tillkommet möjligheten att ange om en variabel är invariabel, utvariabel, eller båda samtidigt. Detta anges med INTENT som kan vara IN, OUT eller INOUT. Om IN gäller så kan det verkliga argumentet vid anropet vara ett uttryck som X+Y eller SIN(X) eller en konstant som 37, eftersom ett värde skall överföras till subprogrammet men ej åter till anropande enhet. Variabeln får i detta fall ej tilldelas något nytt värde i subprogrammet. Om OUT gäller så måste däremot det verkliga argumentet vara en variabel. Vid inträde i subprogrammet anses då variabeln som odefinierad. Det tredje fallet täcker båda möjligheterna, ett värde in, ett annat (eller eventuellt samma) ut. Även då måste naturligtvis det verkliga argumentet vara en variabel. Om argumentet har ett pekar-attribut så får INTENT ej sättas. Implementeringen av INTENT är dock ännu ej fullständig.
Den ena användningen för den nya programenheten MODULE är att ta hand om globala data, och den ersätter då BLOCK DATA, den andra är att paketera nya datatyper.
PROGRAM programnamn
och måste avslutas med
END
eller
END PROGRAM
eller
END PROGRAM programnamn
Observera att ordet PROGRAM måste vara med i END satsen om programnamnet är det!
Det kan vara lämpligt att låta huvudprogrammet vara ett mycket kort program, som bara består av viss grundläggande inläsning samt anrop av en del av de olika subrutiner och funktioner som tillsammans med huvudprogrammet bildar programmet.
Antalet subrutiner i ett program kan vara noll. Det kan finnas externa och interna subrutiner, dessutom kan de fem i Fortran inbyggda (eng. intrinsic) subrutinerna användas.
Antalet funktioner i ett program kan vara noll. Det kan finnas externa och interna funktioner, samt satsfunktioner, dessutom kan de många i Fortran inbyggda (eng. intrinsic) funktionerna användas.
Moduler illustreras här med tre exempel, nämligen en för byte av två variabler (av samma men varierande typ), en för intervallaritmetik och slutligen en som bestämmer vissa standardvärden, bland annat vilken flyttalsprecision (snarare maskinprecision) som skall användas.
Man flyttar över allt som rör SWAP till en modul, vilken sedan kan användas i huvudprogrammet med satsen USE modulnamnet. Notera att i modulens INTERFACE skall den speciella satsen MODULE PROCEDURE användas för att undvika att rutinerna specificeras både i INTERFACE och i CONTAINS. Vid användning måste naturligtvis både modulen och huvudprogrammet länkas ihop, till exempel med satsen f90 del1.f90 del2.f90. Här följer närmast modulen:
MODULE BO
INTERFACE SWAP
MODULE PROCEDURE SWAP_R, SWAP_I, SWAP_C
END INTERFACE
CONTAINS
SUBROUTINE SWAP_R(A,B)
IMPLICIT NONE
REAL, INTENT (INOUT) :: A, B
REAL :: TEMP
TEMP = A ; A = B ; B = TEMP
END SUBROUTINE SWAP_R
SUBROUTINE SWAP_I(A,B)
IMPLICIT NONE
INTEGER, INTENT (INOUT) :: A, B
INTEGER :: TEMP
TEMP = A ; A = B ; B = TEMP
END SUBROUTINE SWAP_I
SUBROUTINE SWAP_C(A,B)
IMPLICIT NONE
CHARACTER, INTENT (INOUT) :: A, B
CHARACTER :: TEMP
TEMP = A ; A = B ; B = TEMP
END SUBROUTINE SWAP_C
END MODULE BO
Här följer så huvudprogrammet, vilket nu är rensat på all ointressant
information om SWAP.
PROGRAM SWAP_HUVUD
USE BO
IMPLICIT NONE
INTEGER :: I, J, K, L
REAL :: A, B, X, Y
CHARACTER :: C, D, E, F
I = 1 ; J = 2 ; K = 100 ; L = 200
A = 7.1 ; B = 10.9 ; X = 11.1; Y = 17.0
C = 'a' ; D = 'b' ; E = '1' ; F = '"'
WRITE (*,*) I, J, K, L, A, B, X, Y, C, D, E, F
CALL SWAP(I,J) ; CALL SWAP(K,L)
CALL SWAP(A,B) ; CALL SWAP(X,Y)
CALL SWAP(C,D) ; CALL SWAP(E,F)
WRITE (*,*) I, J, K, L, A, B, X, Y, C, D, E, F
END
MODULE INTERVALL_ARITMETIK
TYPE INTERVALL
REAL LAEGRE, OEVRE
END TYPE INTERVALL
INTERFACE OPERATOR (+)
MODULE PROCEDURE ADDERA_INTERVALL
END INTERFACE
INTERFACE OPERATOR (-)
MODULE PROCEDURE SUBTRAHERA_INTERVALL
END INTERFACE
INTERFACE OPERATOR (*)
MODULE PROCEDURE MULTIPLICERA_INTERVALL
END INTERFACE
INTERFACE OPERATOR (/)
MODULE PROCEDURE DIVIDERA_INTERVALL
END INTERFACE
CONTAINS
FUNCTION ADDERA_INTERVALL(A,B)
TYPE(INTERVALL), INTENT(IN) :: A, B
TYPE(INTERVALL) :: ADDERA_INTERVALL
ADDERA_INTERVALL%LAEGRE = A%LAEGRE + B%LAEGRE
ADDERA_INTERVALL%OEVRE = A%OEVRE + B%OEVRE
END FUNCTION ADDERA_INTERVALL
FUNCTION SUBTRAHERA_INTERVALL(A,B)
TYPE(INTERVALL), INTENT(IN) :: A, B
TYPE(INTERVALL) :: SUBTRAHERA_INTERVALL
SUBTRAHERA_INTERVALL%LAEGRE = A%LAEGRE - B%OEVRE
SUBTRAHERA_INTERVALL%OEVRE = A%OEVRE - B%LAEGRE
END FUNCTION SUBTRAHERA_INTERVALL
FUNCTION MULTIPLICERA_INTERVALL(A,B)
! POSITIVA TAL FÖRUTSÄTTES
TYPE(INTERVALL), INTENT(IN) :: A, B
TYPE(INTERVALL) :: MULTIPLICERA_INTERVALL
MULTIPLICERA_INTERVALL%LAEGRE = &
A%LAEGRE * B%LAEGRE
MULTIPLICERA_INTERVALL%OEVRE = &
A%OEVRE * B%OEVRE
END FUNCTION MULTIPLICERA_INTERVALL
FUNCTION DIVIDERA_INTERVALL(A,B)
! POSITIVA TAL FÖRUTSÄTTES
TYPE(INTERVALL), INTENT(IN) :: A, B
TYPE(INTERVALL) :: DIVIDERA_INTERVALL
DIVIDERA_INTERVALL%LAEGRE = A%LAEGRE / B%OEVRE
DIVIDERA_INTERVALL%OEVRE = A%OEVRE / B%LAEGRE
END FUNCTION DIVIDERA_INTERVALL
END MODULE INTERVALL_ARITMETIK
Vid kompilering av ovanstående skapas en fil intervall_aritmetik.mod
eller intervall_aritmetik.M som innehåller
en intressant modifierad version av koden ovan.
Ett program som vill utnyttja detta paket inkluderar satsen USE INTERVALL_ARITMETIK först
bland specifikationssatserna, då finns direkt både datatypen INTERVALL och de fyra
räknesätten på denna typ tillgängliga. I en del fall är det önskvärt
att bara inkludera en del av faciliteterna i en modul, detta sker med ONLY enligt nedan.
USE modul_namn, ONLY : lista_över_utvalda_rutiner
Följande är ett exempel på ett mycket enkelt huvudprogram för test av intervallaritmetiken.
Det ligger på filen intervall.f90 eller
intv.f90.
USE INTERVALL_ARITMETIK
IMPLICIT NONE
TYPE (INTERVALL) :: A, B, C, D, E, F
A%LAEGRE = 6.9
A%OEVRE = 7.1
B%LAEGRE = 10.9
B%OEVRE = 11.1
WRITE (*,*) A, B
C = A + B
D = A - B
E = A * B
F = A / B
WRITE (*,*) C, D
WRITE (*,*) E, F
END
Körning av detta program på Sun-dator med NAG:s Fortran 90 kompilator
följer.
f90 intervall.f90 intervall_aritmetik.f90
intervall.f90:
intervall_aritmetik.f90:
a.out
6.9000001 7.0999999 10.8999996 11.1000004
17.7999992 18.2000008 -4.2000003 -3.7999997
75.2099991 78.8100052 0.6216216 0.6513762
Med Suns:s egen SunSoft kompilator använder jag i stället
f90 -c intervall_aritmetik.f90
f90 intervall.f90 intervall_aritmetik.f90
På Digitals system hittar jag det riktiga kommandot som är
f90 intervall_aritmetik.f90 intervall.f90
dvs modulen skall komma före användningen! Detta fungerar även på
SunSoft och NAG!
I ovanstående exempel har betydelsen av bland andra tecknet + generaliserats till att gälla även intervall. Fortran 90 innehåller en spärr mot att definiera om betydelsen av + på vanliga variabler.
(14.2) Komplettera modulen så att paketet utför lämplig felhantering vid division
med ett intervall som innehåller noll.
Lösning.
(14.3) Komplettera även så att hänsyn tas till det lokala avrundningsfelet vid
operationen.
Lösning.
Det följande programmet är ett enkelt exempel på användning av en sådan modul.
MODULE START
IMPLICIT NONE
INTEGER, PARAMETER :: AP = SELECTED_REAL_KIND(14, 300)
REAL (KIND=AP), PARAMETER :: ONE_TENTH = 0.1_AP
END MODULE START
PROGRAM TEST_START
USE START
REAL (KIND=AP) :: PI, X, Y, Z
PI = 4.0_AP*ATAN(1.0_AP)
X = 0.1
Y = 10*(X - ONE_TENTH)
Z = 10*ONE_TENTH - 1.0_AP
WRITE(*,*) X, ONE_TENTH
WRITE(*,*) Y, Z
END PROGRAM TEST_START
Utmatningen visar dels att det hela fungerar, dels vikten av att explicit ange dubbel precision
eller motsvarande vid konstanter som inte är exakta i "kort" precision. Tyvärr har
jag inte hittat något bra sätt att ge pi ett värde i arbetsprecisionen redan i
modulen.
0.1000000014901161 0.1000000000000000 1.4901161138336505E-08 0.0000000000000000E+000
PRIVATE
PUBLIC :: VAR1
blir samtliga variabler utom VAR1 lokala, medan VAR1 blir globalt tillgänglig. Notera att
båda dessa begrepp antingen kan ges som kommandon, till exempel
INTEGER :: IVAR
PRIVATE :: IVAR
eller som attribut
INTEGER, PRIVATE :: IVAR
och motsvarande för PUBLIC.
Denna nya programenhet består av följande element
BLOCK DATA namn
Deklarationer
Datasatser
END
Ett enkelt exempel följer, där det första blocket även sparas. Det är
inte nödvändigt att alla variabler i ett sådant block har initierats, i exemplet
nedan har således inte heltalsvariabeln ANTAL getts något startvärde.
BLOCK DATA START
INTEGER :: VEK(10), ANTAL
CHARACTER*29 :: ALFA
COMMON /BLOCK1/ VEK, ANTAL
COMMON /BLOCK2/ ALFA
SAVE /BLOCK1/
DATA VEK / 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 /
DATA ALFA /'ABCDEFGHIJKLMNOPQRSTUVWXYZÅÄÖ'/
END
INCLUDE 'cls.f'
INCLUDE '../fortran90/cls.f'
INCLUDE '/mailocal/lab/numt/TANA70/cls.f'
Denna sats fanns i flera utvidgningar till Fortran 77 och användes främst för
att lägga in identiska kopior av COMMON-block i flera subrutiner och funktioner. Som tidigare
påpekats är det ju väsentligt att speciellt namnade COMMON-block är identiska
varje gång de uppträder.
Under Fortran 90 kan man med fördel i stället använda moduler. En fördel med att inkludera programfiler med INCLUDE är att det kan röra sig om ofullständiga programavsnitt som uppträder på flera ställen, medan moduler måste följa vissa syntax-regler. Observera dock att sedan avsnittet inkluderats måste resultatet följa Fortrans syntaxregler. I exemplet ovan, som inkluderar en hel subrutin, måste därför INCLUDE satsen antingen ligga först eller efter ett END (svarande mot en programenhet, ej bara END DO eller END IF).
Däremot så är inte ordningen mellan satserna i en programenhet godtycklig. Den första satsen skall vara PROGRAM, SUBROUTINE, FUNCTION, BLOCK DATA eller MODULE, den sista satsen skall vara END. Det gäller dock, för att uppnå kompatibilitet bakåt mot gamla Fortranversioner, att satsen PROGRAM i ett huvudprogram är frivillig.
En programenhet skall vara uppbyggd på följande sätt
Specifikation av programenheten
USE modul
IMPLICIT NONE
Övriga IMPLICIT satser
INTERFACE
Deklarationer
Satsfunktioner
Exekverbar del
CONTAINS
Interna subrutiner och funktioner
END
Det enda som är obligatoriskt är END-satsen, ett helt korrekt huvudprogram (och därmed
även program) kan bestå av enbart satsen END. Det programmet utför naturligtvis
ingenting, det kan sägas vara det enklast möjliga Fortran-programmet, och kan naturligtvis
användas för test. Jag prövade det under NAG:s kompilator på Sun och fick
inga felutskrifter och ett körbart program på 73 728 bytes, medan det under Sun:s Fortran
77 kompilator gav hela 188 416 bytes.
FORMAT-satser kan finnas var som helst mellan USE och CONTAINS, men det är lämpligt att samla dom i början eller slutet eller, vilket är vanligast, i direkt samband med den första läs- eller skrivsats som använder den.
Likaså kan DATA-satser och PARAMETER-satser placeras nästan var som helst, men jag rekommenderar varmt att de placeras bland deklarationerna (eftersom de ej är exekverbara).
Även eventuella ENTRY-satser kan placeras ganska fritt, men här rekommenderar jag inplacering bland de exekverbara satserna.
ENTRY-satsen ger en möjlighet till en alternativ ingång i en subrutin eller funktion. Jag avråder dock bestämt från dess användning.