14. Program-uppbyggnad

14.0 Inledning

Förutom de fyra gamla programenheterna PROGRAM (dvs huvudprogrammet), subrutin, funktion och BLOCK DATA har tillkommet moduler (MODULE) samt en hel del nyheter i de gamla enheterna. Jag upprepar att subprogram är ett sammanfattande namn på subrutin och funktion. Om man är varsam med det svenska språket bör man dock skriva underprogram och underrutin.

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.

14.1 Huvudprogram

Huvudprogrammet är den enda obligatoriska programenheten i ett Fortranprogram, så ger exempelvis Cray:s Fortran 90 kompilator en kraftig varning om huvudprogram saknas vid kompilering. Ett huvudprogram kan inledas med den helt frivilliga satsen
        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.

14.2 Subrutiner

En subrutin (eng. subroutine) är en programenhet som ej returnerar något funktionsvärde genom sitt namn och som har ett antal parametrar (eng. arguments). Det är tillåtet att subrutinen ändrar värdet på ett eller flera av argumenten. En subrutin kan dock ha en annan uppgift, till exempel att läsa in eller skriva ut data. En subrutin har, till skillnad från en funktion, ingen typ.

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.

14.3 Funktioner

En funktion (eng. function) är en programenhet som returnerar ett funktionsvärde (eventuellt flera, dvs ett fält) i sitt namn och som har ett antal parametrar. Det är faktiskt tillåtet med funktioner utan parametrar (i detta fall måste dock parameterlistan ges i form av vänsterparentes och högerparentes, utan något emellan). Det är likaså tillåtet men ej tillrådligt att funktionen ändrar värdet på ett eller flera av argumenten.

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.

14.4 Moduler

En modul innehåller deklarationer (specifikationer) och definitioner som skall användas av de andra program-enheterna. Ersätter BLOCK DATA, som bara kunde utnyttjas för att tilldela initialvärden till storheter i COMMON block, se vidare sektion 14.5.

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.

14.4.1 Enkelt exempel på modul

I sektion 5.2.1 gav jag ett fullständigt exempel på en rutin SWAP(A,B) som byter plats på A och B, utnyttjande olika underliggande rutiner beroende på om de båda variablerna är av typ REAL, INTEGER eller CHARACTER. Användningen av detta var inte så trevlig, eftersom man var tvungen att ha en "massa" INTERFACE som definierade de tre olika fallen. Allt detta kan nu döljas i en modul.

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

14.4.2 Intervallaritmetik

Som ett ganska stort exempel på en modul så försöker jag mig på att införa ett paket för intervallaritmetik. Till varje närmevärde X hör då ett intervall [X_lägre ; X_övre]. Vid användning av paketet vill man ha det så enkelt att man bara ger variabeln X då man menar intervallet. Variabeln X skall då ha en ny datatyp, intervall. Följande ligger på filen intervall_aritmetik.f90 eller intv_ari.f90, som bland annat innehåller ett gränssnitt, INTERFACE.
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.

Övningar.

(14.1) Komplettera modulen så att paketet klarar godtyckliga tecken på talen även vid multiplikation och division.
Lösning.

(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.

14.4.3 Modul för standardparametrar

En trevlig användning av modul är att där stoppa in dels parametrar som ofta användes i beräkningen, dels specifikation av önskad noggrannhet.

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

14.4.4 Diverse om moduler

I en modul kan vissa begrepp definieras som PRIVATE, vilket innebär att programenheter utanför modulen ej kan nå dessa. Ibland utnyttjas även en explicit PUBLIC deklaration, normalt är dock PUBLIC underförstått. Genom att ge följande satser
        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.

14.5 BLOCK DATA programenheter

Variabler i COMMON får ej tilldelas initialvärden med DATA-sats, utom i en speciell programenhet kallad BLOCK DATA. Anledningen till detta är att annars blir det svårt för systemet att avgöra i vilken programenhet som initieringen skall ske.

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

14.6 INCLUDE satsen

INCLUDE kan användas för att inkludera källkod från en extern fil. Konstruktionen är att på en rad finns INCLUDE samt en textsträng samt eventuellt avslutande kommentar, men satsnummer är ej tillåtet. Tolkningen är implementationsberoende, normalt tolkas textsträngen som namnet på den fil som skall inkluderas på det ställe i källkoden där INCLUDE satsen finns. Kapsling är tillåten (antalet nivåer är implementationsberoende), men ej rekursion. Ett par exempel från UNIX följer, där subrutinen i filen cls.f inkluderas, antingen direkt från aktuell filkatalog, eller med en relativ respektive absolut filadress. Däremot har jag inte lyckats att använda symboliska namn från UNIX.
        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).

14.7 Ordningen mellan satser

Ordningen mellan de olika programenheterna är i nästan alla sammanhang godtycklig. I enstaka fall har jag noterat att ett huvudprogram måste vara först, men jag har inte funnit detta krav konsistent, det är bara ibland som kravet funnits på vissa maskiner.

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.

Diverse begrepp från Fortran 77 Innehåll Programbibliotek


Senast modifierad: 27 juli 1999
boein@nsc.liu.se