De inbyggda funktionerna är generiska, dvs datatypen hos argumentet anger vilken rutin som skall väljas. Under Fortran 66 var man tvungen att skriva SIN(X) om X var REAL, DSIN(X) om X var DOUBLE PRECISION och slutligen CSIN(X) om X var COMPLEX. Från Fortran 77 fungerar SIN(X) i alla tre fallen. Om funktionsnamnet användes som argument vid funktions- eller subrutinanrop måste däremot det explicita namnet anges.
Vanliga enkla funktioner diskuterades redan i avsnitt 3.13. Vi skall därför här diskutera funktioner utan argument, fältvärda funktioner och rekursiva funktioner. Vidare skall vi ta upp "avsikten" hos olika argument, frivilliga argument och argument med nyckelord. Användning av funktioner som argument skall likaså diskuteras. Vi börjar med det senare.
REAL FUNCTION NOLL(F, A, B, EPS) IMPLICIT NONE ! Beräknar nollställe till en funktion f(x) om ! a < b och f(a) > 0 och f(b) < 0 och eps > 0. REAL :: F, A, B, EPS REAL :: V, H, MITT V = A H = B DO IF ( H - V >= EPS ) THEN MITT = 0.5*(V+H) IF ( F(MITT) > 0.0 ) THEN V = MITT ELSE H = MITT END IF CYCLE ! Ny iteration behövs. END IF EXIT ! Konvergens har erhållits. END DO NOLL = 0.5*(V+H) RETURN END FUNCTION NOLLFunktionen ovan fungerar så att först testas om vänster- och högerpunkterna ligger tillräckligt nära varandra, i så fall utförs inte IF-satsen och den "eviga" DO-slingan avbrytes med EXIT. Ett slutligt nollställe beräknas som medelvärdet av vänster- och högerpunkterna. Om punkterna ej ligger tillräckligt nära beräknas en mittpunkt, som väljes som ny vänster- eller högerpunkt, och en ny iteration begäres med CYCLE. Funktionen täcker som synes inte alla fall, och svarar inte alltid så tidigt som möjligt. Anledningen är att vi sökt göra den så enkel som möjligt för att illustrera hur funktioner med funktioner som argument fungerar. Ett par utvidgningar föreslås i övningarna nedan.
Den funktion F(X) som användes som argument till funktionen NOLL måste också finnas, och då varken som intern funktion eller som satsfunktion, utan som en extern funktion, eller som en inbyggd funktion. Ett exempel på en extern funktion ges nedan, med namnet G.
REAL FUNCTION G(X) IMPLICIT NONE REAL :: X G = COS(X) - LOG(X) RETURN END FUNCTION GFör att använda dessa funktioner kräves även ett huvudprogram. Härvid är det väsentligt, på grund av Fortrans separatkompilering, att informera systemet om att funktionen G(X) är en extern funktion, vilket sker med satsen EXTERNAL G.
PROGRAM NOLLSTAELLE IMPLICIT NONE REAL :: NOLL, A, B, G, EPS, Y EXTERNAL G A = 1.0 B = 2.0 EPS = 2.0E-7 Y = NOLL(G, A, B, EPS) WRITE(*,*) Y STOP END PROGRAM NOLLSTAELLEOm funktionen i stället är en inbyggd funktion användes satsen INTRINSIC funktion. I exemplet ovan passar det bra att byta ut satsen EXTERNAL G mot INTRINSIC COS och satsen Y = NOLL(G, A, B, EPS) mot Y = NOLL(COS, A, B, EPS), varvid programmet räknar ut att pi/2 är ett nollställe till cosinus. I detta fall behövs naturligtvis inte den externa funktionen G(X). Notera även att den inbyggda funktionen ej skall typdeklareras.
(5.2) Modifiera funktionen NOLL till att klara även andra
teckenkombinationer på f(a) och f(b).
Lösning.
(5.3) Modifiera funktionen NOLL till att använda en mer avancerad
algoritm än intervallhalvering, till exempel regula falsi.
Kommentar.
Som exempel har jag skrivet en enkel funktion utan argument. För att få ut något resultat ur den har jag valt att använda tidsrutinen i Fortran 90 (se Appendix 5). I heltalsvektorn VALUES lagras år, datum och tidpunkt, vilka jag summerar med fältadderaren SUM för att få något som påminner om ett slumptal. Anropet av tidsrutinen sker med nyckelordsargument, vilket förklaras senare.
PROGRAM TEST_INGET_ARGUMENT IMPLICIT NONE REAL :: NOLL_ARG, Y Y = NOLL_ARG() WRITE(*,*) Y STOP END PROGRAM TEST_INGET_ARGUMENT REAL FUNCTION NOLL_ARG() IMPLICIT NONE INTEGER, DIMENSION(8) :: VALUES CALL DATE_AND_TIME(VALUES=VALUES) NOLL_ARG = SUM(VALUES) RETURN END FUNCTION NOLL_ARG
RECURSIVE FUNCTION FAKULTET(N) RESULT (FAK_RESULTAT) IMPLICIT NONE INTEGER, INTENT(IN) :: N INTEGER :: FAK_RESULTAT IF ( N <= 1 ) THEN FAK_RESULTAT = 1 ELSE FAK_RESULTAT = N * FAKULTET(N-1) END IF END FUNCTION FAKULTET RECURSIVE FUNCTION FIBONACCI(N) RESULT (FIBO_RESULTAT) IMPLICIT NONE INTEGER, INTENT(IN) :: N INTEGER :: FIBO_RESULTAT IF ( N <= 2 ) THEN FIBO_RESULTAT = 1 ELSE FIBO_RESULTAT = FIBONACCI(N-1) + FIBONACCI(N-2) END IF END FUNCTION FIBONACCIAnledningen till att ovanstående beräkning av Fibonacci-talen blir så ineffektiv är att anrop med ett visst värde på N genererar två anrop av rutinen, som i sin tur generar fyra, osv. Gamla värden (anrop) återutnyttjas ej!
En intressantare användning av rekursiv teknik är beräkning av exponentialfunktionen av en matris. I stället för det omedelbara uttrycket med successiv multiplikation med en matris kan man använda en rekursiv metod där man plockar ut 2-potenser för att optimera beräkningen. Rekursion är vidare utmärkt för att koda adaptiva algoritmer, se Övning (5.5) nedan.
En annan viktig användning av det nya begreppet resultat-variabel är vid fält-värda funktioner, då det är lätt att deklarera denna variabel att lagra funktionens värde(n). Det är faktiskt kombinationen rekursivitet och fält som tvingat fram det nya begreppet.
I programmet ovan användes det nya begreppet "avsikt" eller INTENT, vilket förklaras i avsnitt 5.2.1.5.
(5.5) Skriv en adaptiv rutin för kvadratur, dvs beräkning av den bestämda integralen
över ett visst intervall.
Lösning.
Fältvärda funktioner kräver den tidigare diskuterade nyheten, nämligen ett explicit gränssnitt (INTERFACE) i den anropande programenheten. Gränssnittet består i princip av deklarationerna i funktionen, se exemplet nedan. Gränssnitt kräves vid flera olika tillfällen och är faktiskt det svåraste i Fortran 90. Se vidare avsnittet 11.2.
Nedanstående enkla funktion ger som resultat en vektor med de första åtta potenserna av det skalära argumentet.
PROGRAM TEST_FAELTVAERD_FUNKTION IMPLICIT NONE INTERFACE ! Detta är gränssnittet för den ! fältvärda funktionen POTENS(X). FUNCTION POTENS(X) RESULT(FAELT) REAL :: X REAL, DIMENSION(8) :: FAELT END FUNCTION POTENS END INTERFACE REAL :: X REAL, DIMENSION(8) :: Y X = 2.0 Y = POTENS(X) WRITE(*,*) Y STOP END PROGRAM TEST_FAELTVAERD_FUNKTION FUNCTION POTENS(X) RESULT(FAELT) IMPLICIT NONE REAL :: X REAL, DIMENSION(8) :: FAELT INTEGER :: I DO I = 1, 8 FAELT(I) = X**I END DO RETURN END FUNCTION POTENSVi kan ganska enkelt justera ovanstående funktion till att klara godtyckliga potenser, där ordningen ges endast som indata i huvudprogrammet. Dimensionen överföres i nästa exempel automatiskt med argumentet, en flyttalsvektor, och dimensionen för resultatet sättes lika med denna dimension. Det går naturligtvis minst lika bra att överföra dimensionen med ett vanligt heltalsargument.
Det är inte nödvändigt att här låta argumentet vara en flyttalsvektor. Man kan även klara sig med ett skalärt argument, men då måste man använda pekare vid deklarationen av fältet, se sektion 4.3.2.
PROGRAM NY_TEST_FAELTVAERD_FUNKTION IMPLICIT NONE INTERFACE FUNCTION POTENS(X) RESULT(FAELT) REAL, DIMENSION(:) :: X REAL, DIMENSION(SIZE(X)) :: FAELT END FUNCTION POTENS END INTERFACE REAL, DIMENSION(:), ALLOCATABLE :: X REAL, DIMENSION(:), ALLOCATABLE :: Y INTEGER :: DIM WRITE(*,*) " Ge ordning " READ(*,*) DIM ALLOCATE(X(DIM)) ALLOCATE(Y(DIM)) X = 2.0 ! Detta är en vektoroperation. Y = POTENS(X) WRITE(*,*) Y STOP END PROGRAM NY_TEST_FAELTVAERD_FUNKTION FUNCTION POTENS(X) RESULT(FAELT) IMPLICIT NONE REAL, DIMENSION(:) :: X REAL, DIMENSION(SIZE(X)) :: FAELT INTEGER :: I DO I = 1, SIZE(X) FAELT(I) = X(I)**I END DO RETURN END FUNCTION POTENS
Om argumentet har ett pekar-attribut POINTER så får INTENT ej sättas.
PROGRAM TEST_AV_AVSIKT IMPLICIT NONE INTERFACE FUNCTION FUN(X,Y,Z) RESULT(UT) REAL :: UT REAL, INTENT(IN) :: X REAL, INTENT(OUT) :: Y REAL, INTENT(INOUT) :: Z END FUNCTION FUN END INTERFACE REAL :: Y, Z Z = 12.0 WRITE(*,*) Z Y = FUN(1.0,Y,Z) WRITE(*,*) Y, Z STOP END PROGRAM TEST_AV_AVSIKT FUNCTION FUN(X,Y,Z) RESULT (UT) ! Varning! Denna funktion har sido-effekter, vilket är ! olämpligt. Sidoeffekterna är här för att illustrera ! avsikt eller INTENT. IMPLICIT NONE REAL, INTENT(IN) :: X REAL, INTENT(OUT) :: Y REAL, INTENT(INOUT) :: Z REAL :: UT Y = SIN(X) + COS(Z) Z = SIN(X) - COS(Z) UT = Y + Z RETURN END FUNCTION FUNOm man ovan exempelvis byter ut Y = FUN(1.0,Y,Z) mot Y = FUN(1.0,Y,3.0) klagar NAG:s Fortran 90 kompilator på att det ej getts något ut-argument på plats 3.
Användningen av nyckelord och underförstådda argument är ett av de fall då ett explicit gränssnitt INTERFACE erfordras. Jag ger därför här ett fullständigt exempel. Som nyckelord användes de formella parametrarna i gränssnittet, vilka ej behöver ha samma namn som de i den verkliga subrutinen. Dessa nyckelord skall ej deklareras i anropande programenhet.
IMPLICIT NONE INTERFACE FUNCTION SUMMA (A, B, N) RESULT (UT) REAL :: UT INTEGER, INTENT (IN) :: N REAL, INTENT(OUT) :: A REAL, INTENT(IN), OPTIONAL :: B END FUNCTION SUMMA END INTERFACE REAL :: X, Y Y = SUMMA(X,100.0,20) ! Normalt anrop WRITE(*,*) X, Y Y = SUMMA(B=10.0,N=50,A=X) ! Anrop med nyckelord WRITE(*,*) X, Y Y = SUMMA(B=10.0,A=X,N=100) ! Anrop med nyckelord WRITE(*,*) X, Y Y = SUMMA(N=100,A=X) ! Anrop med nyckelord ! och ett skönsvärde. WRITE(*,*) X, Y END FUNCTION SUMMA(A,B,N) RESULT (UT) IMPLICIT NONE REAL :: A, UT REAL, OPTIONAL, INTENT (IN) :: B INTEGER :: N REAL :: TEMP_B IF (PRESENT(B)) THEN TEMP_B = B ELSE TEMP_B = 20.0 END IF A = TEMP_B + N UT = A + TEMP_B + N RETURN ENDNotera att IMPLICIT NONE för huvudprogrammet ej verkar i funktionen SUMMA, varför denna har kompletterats med det kommandot och deklaration av de ingående variablerna.
Den inbyggda funktionen PRESENT är sann om dess argument finns med i anropet, och falsk om det inte finns med. I det senare fallet användes i stället skönsvärdet (eng. the default value).
Körning på UNIX sker med
f90 program.f90 a.out 1.2000000E+02 2.4000000E+02 60.0000000 1.2000000E+02 1.1000000E+02 2.2000000E+02 1.2000000E+02 2.4000000E+02Gränssnittet INTERFACE placeras lämpligen i en modul, så att användaren inte behöver bry sig. Gränssnitten blir ett naturligt komplement till rutinbiblioteken, Fortran 90 söker automatiskt efter moduler i aktuell filkatalog, eventuella filkataloger i I-listan, samt /usr/local/lib/f90. Begreppet I-lista förklaras i Appendix 6. Om man glömmer INTERFACE eller har ett felaktigt sådant erhålles ofta felet "Segmentation error". Detta fel kan dock även erhållas vid ett i princip korrekt INTERFACE, men då man glömt att allokera något av de ingående fälten i den anropande programenheten.
Notera att om en utmatningsvariabel anges som OPTIONAL och INTENT (OUT) så måste den vara med i anropslistan om programmet vid exekveringen lägger ut ett värde på denna variabel. Man måste därför i sitt program använda test med PRESENT av aktuell variabel, och endast om den är med i anropet använda den för tilldelning, för att på så sätt få den önskade valfriheten om man bara ibland vill ha ut en viss variabel.
Grundprincipen för de inbyggda generiska funktionerna tål dock att upprepas, dvs att datatypen hos argumentet anger vilken rutin som skall väljas. Under Fortran 66 var man tvungen att skriva SIN(X) om X var REAL, DSIN(X) om X var DOUBLE PRECISION och slutligen CSIN(X) om X var COMPLEX för att få resultat av rätt typ och noggrannhet. Från Fortran 77 fungerar SIN(X) i alla tre fallen.
Jag ger här ett fullständigt exempel på en generisk subrutin, nämligen en rutin SWAP(A,B) som byter plats på värdena hos variablerna A och B, utnyttjande olika underliggande rutiner beroende på om de båda argumenten är av typ REAL, INTEGER eller CHARACTER.
Subrutinen finns således i detta fall i tre varianter, nämligen för flyttal, heltal och textsträng (med längden ett). De är skrivna på vanligt sätt och heter SWAP_R, SWAP_I respektive SWAP_C. Samtliga är dessutom med i huvudprogrammets INTERFACE med sina deklarationer, men detta gränssnitt har namnet SWAP. När man i huvudprogrammet vill utnyttja någon av subrutinerna använder man enbart namnet SWAP och argument av viss typ. Systemet väljer då bland de tre tillgängliga funktionerna ut den som svarar mot argumenten i anropet. Om dessa båda argument är av olika typ blir det ett fel.
PROGRAM SWAP_HUVUD IMPLICIT NONE INTEGER :: I, J, K, L REAL :: A, B, X, Y CHARACTER :: C, D, E, F INTERFACE SWAP SUBROUTINE SWAP_R(A,B) REAL, INTENT (INOUT) :: A, B END SUBROUTINE SWAP_R SUBROUTINE SWAP_I(A,B) INTEGER, INTENT (INOUT) :: A, B END SUBROUTINE SWAP_I SUBROUTINE SWAP_C(A,B) CHARACTER, INTENT (INOUT) :: A, B END SUBROUTINE SWAP_C END INTERFACE 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 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_COvanstående fungerar utmärkt, men användaren är inte så glad åt att behöva släpa med all information om SWAP och dess olika varianter i sitt program. Lösningen är att flytta över allt som rör SWAP till en modul, vilken sedan kan användas i huvudprogrammet med satsen USE modulnamnet. Vi återkommer till detta i kapitel 14.
För de lokala funktionerna och subrutinerna gäller att de ej kan användas som argument vid anrop av externa funktioner eller subrutiner (eller andra lokala).
IMPLICIT NONE REAL :: X, Y, PI, COT, NCOT ! Efter deklarationer kan satsfunktioner placeras. Y(X) = X + 2.0*X - 3.0*X**2 COT(X) = 1.0/TAN(X) NCOT(X) = COT(PI*X)/PI ! Nu följer de exekverbara satserna. PI = 3.141592654 READ(*,*) X WRITE(*,*) X, Y(X), COT(X), NCOT(X) ENDHär är satsen PI = 3.14159264 den första exekverbara satsen, men den borde nog hellre ha placerats bland deklarationerna utnyttjande PARAMETER satsen eller attributet. Denna form ger dock en god information om att satsfunktionerna utnyttjar argument (här X) och konstanter (här PI) som tilldelas senare, men satsfunktioner som definierats tidigare! De kan naturligtvis även använda konstanter och variabler som initierats redan bland deklarationerna.
Fördelarna gentemot en extern funktion är dels att lokala variabler delas med den aktuella programenheten, dels att funktionsnamnet blir lokalt (vilket innebär minskad risk för namnkonflikt).
Ett enkelt exempel följer, nämligen en omskrivning av satsfunktionerna ovan. Notera att de tre interna funktionerna Y, COT och NCOT nu ej får deklareras på rad 3, den med REAL :: X, PI.
PROGRAM INTERN IMPLICIT NONE REAL :: X, PI ! Nu följer de exekverbara satserna. PI = 3.141592654 READ(*,*) X WRITE(*,*) X, Y(X), COT(X), NCOT(X) CONTAINS ! Här placeras interna funktioner och subrutiner. FUNCTION Y(X) REAL :: Y REAL, INTENT(IN) :: X Y = X + 2.0*X - 3.0*X**2 END FUNCTION Y FUNCTION COT(X) REAL :: COT REAL, INTENT(IN) :: X COT = 1.0/TAN(X) END FUNCTION COT FUNCTION NCOT(X) REAL :: NCOT REAL, INTENT(IN) :: X NCOT = COT(PI*X)/PI END FUNCTION NCOT END PROGRAM INTERNI verkligheten kan de olika interna funktionerna och subrutinerna vara mycket komplicerade enheter med alla satser i Fortran, de kan dock ej i sin tur innehålla interna funktioner eller subrutiner.
OBS! Var försiktig vid deklaration av interna funktioner och subrutiner. De deklareras i samband med att de införes, de skall ej ges någon "egen" deklaration.
Ett eventuellt IMPLICIT NONE i den överordnade programenheten gäller automatiskt även alla interna funktioner och subrutiner, liksom naturligtvis för eventuella satsfunktioner.
(5.7) Skriv det gränssnitt (interface) som behövs i anropande
rutin för att kunna
använda ovanstående integrationsrutin.
Lösning.