Referencegennemsigtighed

Den aktuelle version af siden er endnu ikke blevet gennemgået af erfarne bidragydere og kan afvige væsentligt fra den version , der blev gennemgået den 30. november 2015; checks kræver 9 redigeringer .

Referentiel gennemsigtighed og referenceopacitet er egenskaber ved dele af computerprogrammer . Et udtryk siges at være referentielt transparent, hvis det kan erstattes af den tilsvarende værdi uden at ændre programmets adfærd. Referencegennemsigtige funktioner evalueres til samme værdi for de samme argumenter. Sådanne funktioner kaldes rene funktioner .

I matematik er alle funktioner referentielt transparente ifølge definitionen af ​​en matematisk funktion . Men i programmering er dette ikke altid tilfældet. For at yderligere semantiske associationer af ordet "funktion" ikke er vildledende, bruges udtrykkene " procedure " og " metode " ofte. Ved funktionel programmering tages der kun hensyn til referencegennemsigtige funktioner. Nogle programmeringssprog giver et middel til at garantere referencegennemsigtighed. Nogle funktionelle programmeringssprog giver referencegennemsigtighed for alle funktioner.

Vigtigheden af ​​referentiel gennemsigtighed er, at det giver programmøren og compileren mulighed for at ræsonnere om programmets adfærd som et omskrivningssystem . Det kan hjælpe med validering, algoritmeforenkling , hjælpe med kodemodifikation uden at bryde den, eller kodeoptimering via memoisering , fjernelse af almindeligt underudtryk , doven evaluering eller parallelisering .

Fordi linkgennemsigtighed kræver de samme resultater for et givet sæt af input på et givet tidspunkt, er et referentielt transparent udtryk derfor deterministisk.

Historie

Dette koncept (selv om det ikke er et udtryk) ser ud til at være opstået med Alfred North Whitehead og i Principles of Mathematics af Bertrand Russell (1910-13). Det blev overtaget til analytisk filosofi af Willard Van Orman Quine . I Word and Object (1960) giver Quine denne definition:

En indeslutningstilstand φ er referentielt gennemsigtig, hvis hver gang en forekomst af en entalsterm t er rent referentiel i en term eller sætning ψ(t), er den også rent referentiel i det indeholdende ord eller sætning φ(ψ(t)).

Udtrykket dukkede op i dets moderne brug, når man diskuterede variabler i programmeringssprog, i Christopher Stracheys originale forelæsningssæt.« Grundlæggende begreber i programmeringssprog» (1967). Bibliografien nævner Quines ord og objekt.

Eksempler og modeksempler

Hvis alle de funktioner, der er involveret i et udtryk, er rene funktioner, så er udtrykket referentielt transparent. Nogle urene funktioner kan også indgå i et udtryk, hvis deres værdier kasseres, og deres bivirkninger ikke er signifikante.

Overvej en funktion, der returnerer data fra en eller anden kilde. I pseudokode kunne et kald til denne funktion være GetInput (Source), hvor Sourcekunne angive en specifik diskfil, tastatur osv. Selv med de samme værdier Sourcevil successive returværdier være forskellige. Så funktionen GetInput ()er ikke deterministisk eller referentielt transparent.

Et mere subtilt eksempel er en funktion, der har en fri variabel , dvs. den afhænger af noget input, der ikke eksplicit sendes som en parameter. Dette løses derefter i henhold til reglerne for binding af et navn til en ikke-lokal variabel såsom en global variabel , en variabel i det aktuelle eksekveringsmiljø (til dynamisk binding) eller en variabel i en lukning (til statisk binding). Da denne variabel kan ændres uden at ændre de værdier, der sendes som en parameter, kan resultaterne af efterfølgende kald til funktionen variere, selvom parametrene er identiske. Men i rent funktionel programmering er destruktiv tildeling ikke tilladt, og hvis en fri variabel er statisk bundet til en værdi, har funktionen stadig referentiel transparens, da en ikke-lokal variabel ikke kan ændre sig på grund af dens statiske binding.

Aritmetiske operationer er referentielt gennemsigtige: for eksempel kan 5 * 5du erstatte med 25. Faktisk er alle funktioner i matematisk forstand referentielt transparente: sin (x)er gennemsigtige, da det altid vil give det samme resultat for hver enkelt x.

Opgaver er ikke gennemsigtige. For eksempel ændrer et C -udtryk x = x + 1den værdi, der er tildelt variablen x. Hvis man antager, at det xi første omgang har en værdi 10, giver to på hinanden følgende evalueringer af udtrykket henholdsvis 11, og 12. Det er klart, at udskiftning x = x + 1med 11eller 12vil få udtrykket til at have forskellige værdier for det samme udtryk. Derfor er et sådant udtryk ikke referentielt gennemsigtigt. Men at kalde en funktion som int plusone (int x) {return x + 1;}er gennemsigtig, fordi den ikke implicit ændrer inputværdien xog derfor ikke har disse bivirkninger .

Funktionen today()er ikke referentielt gennemsigtig. Hvis du beregner denne funktion og erstatter den med en værdi (f.eks. "1. januar 2001"), vil i morgen, kørsel today(), ikke få det samme resultat. Dette skyldes, at værdien returneret af funktionen afhænger af tilstanden (datoen).

På sprog uden bivirkninger, såsom Haskell , kan vi erstatte lighed med lighed, fordi f(x) = f(x)for enhver værdi af x. Men dette gælder ikke for sprog med bivirkninger.

Kontrast med imperativ programmering

Hvis erstatningen af ​​et udtryk med dets værdi kun er gyldig på et bestemt tidspunkt i programmet, så er udtrykket ikke referentielt transparent. Definitionen og rækkefølgen af ​​disse sekvenspunkter er det teoretiske grundlag for imperativ programmering og en del af semantikken i et imperativt programmeringssprog.

Men da et referentielt transparent udtryk kan evalueres til enhver tid, er der ikke behov for at specificere sekvenspunkter eller nogen garanti for evalueringsrækkefølge overhovedet. Programmering uden garantier for evalueringsrækkefølgen kaldes ren funktionel programmering.

En af fordelene ved at skrive kode i en referentielt gennemsigtig stil er, at det gør compileren smartere, gør statisk kodeanalyse lettere og giver mulighed for automatiske kodeforbedrende transformationer . For eksempel, når der programmeres i C, vil ydeevnen falde, hvis der er et dyrt funktionskald inde i løkken. Dette på trods af, at opkaldet til denne funktion kan flyttes uden for løkken, mens programmets resultater forbliver uændrede. Programmøren skal derefter manuelt flytte koden, der indeholder opkaldet, muligvis på bekostning af læsbarheden. Men hvis compileren kan bestemme, at en funktion er referentielt transparent, så kan den udføre denne konvertering automatisk.

Den største ulempe ved sprog med referentiel gennemsigtighed er, at de gør udtryk, der naturligt passer til en sekventiel imperativ programmeringsstil, mere akavet og mindre kortfattet. Sådanne sprog inkluderer ofte mekanismer til at lette disse opgaver og samtidig bevare sprogets rent funktionelle kvalitet, såsom visse grammatiske udtryk og monader .

Et andet eksempel

Lad os bruge to funktioner som et eksempel, den ene er referentielt uigennemsigtig og den anden er referentielt gennemsigtig:

int globalValue = 0 ; int rq ( int x ) { globalValue ++ ; return x + globalValue ; } int rt ( int x ) { returner x + 1 ; }

Funktionen rter referentielt transparent, hvilket betyder, at rt(x) = rt(y)hvis x = y. For eksempel rt(6) = 6 + 1 = 7, rt(4) = 4 + 1 = 5osv. Vi kan dog ikke sige det samme for rq, fordi den bruger en global variabel, som den ændrer.

Referenceopacitet rqgør det vanskeligt at ræsonnere om programmer. Antag for eksempel, at vi vil forklare følgende udsagn:

heltal p = rq ( x ) + rq ( y ) * ( rq ( x ) - rq ( x ));

Det kan være fristende at forenkle dette udsagn:

heltal p = rq ( x ) + rq ( y ) * ( 0 ); heltal p = rq ( x ) + 0 ; heltal p = rq ( x );

Dette vil dog ikke fungere for rq(), fordi hver forekomst rq(x)evalueres til en anden værdi. Husk, at den returnerede værdi rqer taget fra en global variabel, som ikke videregives, og som ændres med hvert kald til rq. Det betyder, at matematiske identiteter som f.eks. x - x = 0 {\ displaystyle x-x = 0} x-x = 0ikke længere er gyldige.

Sådanne matematiske identiteter vil gælde for referentielt transparente funktioner som f.eks rt.

En mere kompleks analyse kan dog bruges til at forenkle påstanden:

heltal a = globalVærdi ; heltal p = x + a + 1 + ( y + a + 2 ) * ( x + a + 3 - ( x + a + 4 )); globalValue = globalValue + 4 ; heltal a = globalVærdi ; heltal p = x + a + 1 + ( y + a + 2 ) * ( x + a + 3 - x - a - 4 )); globalValue = globalValue + 4 ; heltal a = globalVærdi ; heltal p = x + a + 1 + ( y + a + 2 ) * -1 ; globalValue = globalValue + 4 ; heltal a = globalVærdi ; heltal p = x + a + 1 - y - a - 2 ; globalValue = globalValue + 4 ; heltal p = x - y - 1 ; globalValue = globalValue + 4 ;

Dette kræver flere trin og kræver en vis grad af kodeforståelse, som ikke kan bruges til at optimere compileren.

Derfor giver referencegennemsigtighed os mulighed for at tænke over vores kode, hvilket fører til mere pålidelige programmer, evnen til at finde fejl, som vi ikke kan finde under test, og bevidstheden om optimeringsmuligheder.