Referenstransparens och referensopacitet är egenskaper hos delar av datorprogram . Ett uttryck sägs vara referenstransparent om det kan ersättas med motsvarande värde utan att ändra programmets beteende. Referenstransparenta funktioner utvärderas till samma värde för samma argument. Sådana funktioner kallas rena funktioner .
Inom matematiken är alla funktioner referenstransparenta enligt definitionen av en matematisk funktion . Men i programmering är detta inte alltid fallet. För att ytterligare semantiska associationer av ordet "funktion" inte ska vara vilseledande, används ofta termerna " procedur " och " metod ". Vid funktionell programmering beaktas endast referenstransparenta funktioner. Vissa programmeringsspråk tillhandahåller ett sätt att garantera referenstransparens. Vissa funktionella programmeringsspråk ger referenstransparens för alla funktioner.
Vikten av referenstransparens är att den tillåter programmeraren och kompilatorn att resonera om programmets beteende som ett omskrivningssystem . Det kan hjälpa till med validering, algoritmförenkling , hjälp med kodmodifiering utan att bryta den, eller kodoptimering via memoisering , borttagning av vanligt underuttryck , lat utvärdering eller parallellisering .
Eftersom länktransparens kräver samma resultat för varje given uppsättning indata vid varje given tidpunkt, är därför ett referenstransparent uttryck deterministiskt.
Detta koncept (även om det inte är en term) verkar ha sitt ursprung hos Alfred North Whitehead och i principer om matematik av Bertrand Russell (1910-13). Det antogs till analytisk filosofi av Willard Van Orman Quine . I Word and Object (1960) ger Quine denna definition:
En inneslutningsmod φ är referensmässigt transparent om närhelst en förekomst av en singulär term t är rent referens i en term eller mening ψ(t), den också är rent referensmässig i det innehållande ordet eller meningen φ(ψ(t)).
Termen dök upp i sin moderna användning, när man diskuterade variabler i programmeringsspråk, i Christopher Stracheys ursprungliga uppsättning föreläsningar.« Grundläggande begrepp i programmeringsspråk» (1967). Bibliografin nämner Quines ord och objekt.
Om alla funktioner som är involverade i ett uttryck är rena funktioner, är uttrycket referenstransparent. Vissa orena funktioner kan också inkluderas i ett uttryck om deras värden kasseras och deras biverkningar inte är signifikanta.
Tänk på en funktion som returnerar data från någon källa. I pseudokod kan ett anrop till denna funktion vara GetInput (Source), där Sourcekan specificera en specifik diskfil, tangentbord, etc. Även med samma värden Sourcekommer successiva returvärden att vara olika. Så funktionen GetInput ()är inte deterministisk eller referenstransparent.
Ett mer subtilt exempel är en funktion som har en fri variabel , dvs den beror på någon indata som inte uttryckligen skickas som en parameter. Detta löses sedan enligt reglerna för att binda ett namn till en icke-lokal variabel såsom en global variabel , en variabel i den aktuella exekveringsmiljön (för dynamisk bindning) eller en variabel i en stängning (för statisk bindning). Eftersom denna variabel kan ändras utan att ändra värdena som skickas som en parameter, kan resultaten av efterföljande anrop till funktionen skilja sig, även om parametrarna är identiska. Men i rent funktionell programmering är destruktiv tilldelning inte tillåten, och därför, om en fri variabel är statiskt bunden till ett värde, har funktionen fortfarande referenstransparens, eftersom en icke-lokal variabel inte kan ändras på grund av sin statiska bindning.
Aritmetiska operationer är referenstransparenta: 5 * 5du kan till exempel ersätta med 25. Faktum är att alla funktioner i matematisk mening är referenstransparenta: sin (x)är transparenta, eftersom det alltid kommer att ge samma resultat för varje enskild x.
Uppdragen är inte transparenta. Till exempel ändrar ett C -uttryck x = x + 1värdet som tilldelats variabeln x. Om man antar att det xinitialt har ett värde 10, ger två på varandra följande utvärderingar av uttrycket respektive 11, och 12. Uppenbarligen kommer att ersätta x = x + 1med 11eller 12göra att uttrycket har olika värden för samma uttryck. Därför är ett sådant uttryck inte referenstransparent. Att anropa en funktion som int plusone (int x) {return x + 1;}är transparent eftersom den inte implicit kommer att ändra ingångsvärdet xoch därför inte har dessa biverkningar .
Funktionen today()är inte referenstransparent. Om du beräknar den här funktionen och ersätter den med ett värde (till exempel "1 januari 2001"), kommer imorgon, körning today(), inte att få samma resultat. Detta beror på att värdet som returneras av funktionen beror på tillståndet (datum).
I språk utan biverkningar, som Haskell , kan vi ersätta jämlikhet med jämlikhet, eftersom f(x) = f(x)för varje värde av x. Men detta gäller inte språk med biverkningar.
Om ersättningen av ett uttryck med dess värde endast är giltigt vid en viss punkt i programmet, är uttrycket inte referenstransparent. Definitionen och ordningen av dessa sekvenspunkter är den teoretiska grunden för imperativ programmering och en del av semantiken i ett imperativt programmeringsspråk.
Men eftersom ett referenstransparent uttryck kan utvärderas när som helst, finns det inget behov av att specificera sekvenspunkter eller någon garanti för utvärderingsordning alls. Programmering utan garantier i utvärderingsordningen kallas rent funktionell programmering.
En av fördelarna med att skriva kod i en referenstransparent stil är att det gör kompilatorn smartare, gör statisk kodanalys enklare och möjliggör automatiska kodförbättrande transformationer . Till exempel, vid programmering i C kommer prestandan att minska om det finns ett dyrt funktionsanrop inne i slingan. Detta trots att anropet till denna funktion kan flyttas utanför slingan medan resultatet av programmet förblir oförändrat. Programmeraren måste sedan manuellt flytta koden som innehåller anropet, möjligen på bekostnad av läsbarheten. Men om kompilatorn kan fastställa att en funktion är referenstransparent, kan den utföra denna konvertering automatiskt.
Den största nackdelen med språk med referenstransparens är att de gör uttryck som naturligt passar en sekventiell imperativ programmeringsstil mer besvärliga och mindre koncisa. Sådana språk inkluderar ofta mekanismer för att underlätta dessa uppgifter samtidigt som språkets rent funktionella kvalitet bibehålls, såsom vissa grammatiska uttryck och monader .
Låt oss använda två funktioner som exempel, en är referensmässigt ogenomskinlig och den andra är referenstransparent:
int globalValue = 0 ; int rq ( int x ) { globalValue ++ ; return x + globalValue ; } int rt ( int x ) { returnera x + 1 ; }Funktionen rtär referenstransparent, vilket betyder att rt(x) = rt(y)om x = y. Till exempel, rt(6) = 6 + 1 = 7, rt(4) = 4 + 1 = 5etc. Vi kan dock inte säga detsamma för rq, eftersom den använder en global variabel som den modifierar.
Referensopacitet rqgör det svårt att resonera om program. Anta till exempel att vi vill förklara följande påstående:
heltal p = rq ( x ) + rq ( y ) * ( rq ( x ) - rq ( x ));Det kan vara frestande att förenkla detta uttalande:
heltal p = rq ( x ) + rq ( y ) * ( 0 ); heltal p = rq ( x ) + 0 ; heltal p = rq ( x );Detta kommer dock inte att fungera för rq(), eftersom varje förekomst rq(x)utvärderas till ett annat värde. Kom ihåg att värdet som returneras rqär hämtat från en global variabel, som inte skickas och som ändras med varje anrop till rq. Detta innebär att matematiska identiteter som x - x = 0 {\ displaystyle x-x = 0} x-x = 0inte längre är giltiga.
Sådana matematiska identiteter kommer att gälla för referenstransparenta funktioner som rt.
En mer komplex analys kan dock användas för att förenkla påståendet:
heltal a = globalValue ; heltal p = x + a + 1 + ( y + a + 2 ) * ( x + a + 3 - ( x + a + 4 )); globalValue = globalValue + 4 ; heltal a = globalValue ; heltal p = x + a + 1 + ( y + a + 2 ) * ( x + a + 3 - x - a - 4 )); globalValue = globalValue + 4 ; heltal a = globalValue ; heltal p = x + a + 1 + ( y + a + 2 ) * -1 ; globalValue = globalValue + 4 ; heltal a = globalValue ; heltal p = x + a + 1 - y - a - 2 ; globalValue = globalValue + 4 ; heltal p = x - y - 1 ; globalValue = globalValue + 4 ;Detta kräver fler steg och kräver en viss grad av kodförståelse som inte kan användas för att optimera kompilatorn.
Därför tillåter referenstransparens oss att tänka på vår kod, vilket leder till mer tillförlitliga program, möjligheten att hitta fel som vi inte kan hitta under testning och medvetenhet om optimeringsmöjligheter.