Quali sono i vantaggi della trasparenza referenziale per un programmatore?


18

Nella programmazione, quali sono i vantaggi della trasparenza referenziale ?

RT fa una delle maggiori differenze tra paradigmi funzionali e imperativi, ed è spesso usato dai sostenitori del paradigma funzionale come un chiaro vantaggio rispetto a quello imperativo; ma in tutti i loro sforzi, questi sostenitori non spiegano mai perché sia ​​un vantaggio per me programmatore .

Certo, avranno le loro spiegazioni accademiche su quanto sia "puro" ed "elegante", ma come lo rende migliore di un codice meno "puro"? Come mi avvantaggia nella mia programmazione quotidiana?

Nota: questo non è un duplicato di Cos'è la trasparenza referenziale? Quest'ultimo affronta il tema di ciò che è RT, mentre questa domanda affronta i suoi benefici (che potrebbero non essere così intuitivi).




3
La trasparenza referenziale consente di utilizzare il ragionamento equazionale per: 1) Dimostrare le proprietà del codice e 2) scrivere programmi. Ci sono alcuni libri su Haskell in cui gli autori dovrebbero sapere come si può partire da alcune equazioni per cui si desidera che una funzione funzioni il fullfil e, usando solo il ragionamento equazionale, si ottiene un'implementazione di tale funzione, che è quindi sicuramente corretta. Ora, quanto può essere applicato nella programmazione "quotidiana" dipende probabilmente dal contesto ...
Bakuriu,

2
@err Ti piace il codice che è più facile da refactoring perché sai se chiamare una funzione due volte è lo stesso che memorizzarne il valore in una variabile, quindi usare quella variabile due volte? Diresti che è un vantaggio per la tua programmazione quotidiana?
Andres F.

Il vantaggio è che non è necessario perdere tempo a pensare alla non trasparenza di riferimento. Mi piace come i vantaggi delle variabili siano che non è necessario perdere tempo a pensare all'allocazione dei registri.
user253751

Risposte:


37

Il vantaggio è che le funzioni pure rendono più facile ragionare sul codice. O, in altre parole, gli effetti collaterali aumentano la complessità del codice.

Prendi un esempio di computeProductPricemetodo.

Un metodo puro ti richiederebbe una quantità di prodotto, una valuta, ecc. Sai che ogni volta che il metodo viene chiamato con gli stessi argomenti, produrrà sempre lo stesso risultato.

  • Puoi persino memorizzarlo nella cache e utilizzare la versione cache.
  • Puoi renderlo pigro e rimandare la sua chiamata a quando ne hai effettivamente bisogno, sapendo che il valore non cambierà nel frattempo.
  • Puoi chiamare il metodo più volte, sapendo che non avrà effetti collaterali.
  • Puoi ragionare sul metodo stesso in un isolamento dal mondo, sapendo che tutto ciò di cui ha bisogno sono gli argomenti.

Un metodo non puro sarà più complesso da utilizzare ed eseguire il debug. Dal momento che dipende dallo stato delle variabili oltre agli argomenti e eventualmente dalla loro modifica, significa che potrebbe produrre risultati diversi quando viene chiamato più volte o non avere lo stesso comportamento quando non viene chiamato affatto o chiamato troppo presto o troppo tardi.

Esempio

Immagina che ci sia un metodo nel framework che analizza un numero:

decimal math.parse(string t)

Non ha trasparenza referenziale, perché dipende da:

  • La variabile di ambiente che specifica il sistema di numerazione, ovvero Base 10 o qualcos'altro.

  • La variabile all'interno della mathlibreria che specifica la precisione dei numeri da analizzare. Quindi, con il valore di 1, l'analisi della stringa "12.3456"darà 12.3.

  • La cultura, che definisce la formattazione prevista. Ad esempio, con fr-FR, l'analisi "12.345"fornirà 12345, perché il carattere di separazione dovrebbe essere ,, non.

Immagina quanto sarebbe facile o difficile lavorare con tale metodo. Con lo stesso input, puoi ottenere risultati radicalmente diversi a seconda del momento in cui chiami il metodo, perché qualcosa, da qualche parte, ha cambiato la variabile d'ambiente o ha cambiato la cultura o impostato una precisione diversa. Il carattere non deterministico del metodo porterebbe a più bug e a un incubo di debug più intenso. Chiamare math.parse("12345")e ottenere 5349come risposta poiché un codice parallelo analizzava i numeri ottali non è carino.

Come risolvere questo metodo ovviamente rotto? Introducendo la trasparenza referenziale. In altre parole, eliminando lo stato globale e spostando tutto sui parametri del metodo:

decimal math.parse(string t, base=10, precision=20, culture=cultures.en_us)

Ora che il metodo è puro, sai che non importa quando lo chiami, produrrà sempre lo stesso risultato per gli stessi argomenti.


4
Solo un addendum: la trasparenza referenziale si applica a tutte le espressioni in una lingua, non solo alle funzioni.
gardenhead,

3
Nota che ci sono delle limitazioni su quanto puoi essere trasparente. Rendere packet = socket.recv()referenzialmente trasparente piuttosto sconfigge il punto della funzione.
Segna il

1
Dovrebbe essere culture = culture.invariante. A meno che non si desideri creare accidentalmente software che funzioni correttamente solo negli Stati Uniti.
user253751

@immibis: hm, bella domanda. Quali sarebbero le regole di analisi per invariant? O sono uguali a en_us, nel qual caso, perché preoccuparsi, o corrispondono a qualche altro paese, nel qual caso, quale e perché questo invece di en_us, oppure hanno le loro regole specifiche che non corrispondono in nessun paese comunque , che sarebbe inutile. Non esiste davvero una "risposta vera" tra 12,345.67e 12 345,67: qualsiasi "regola predefinita" funzionerà per alcuni paesi e non funzionerà per altri.
Arseni Mourzenko,

3
@ArseniMourzenko In genere è un "minimo comune denominatore" e simile alla sintassi utilizzata da molti linguaggi di programmazione (che è anche cultura-invariante). 12345analizza come 12345 12 345o 12,345o 12.345è un errore. 12.345analizzato come un numero a virgola mobile invariante produce sempre 12.345, in conformità con la convenzione del linguaggio di programmazione dell'uso. come separatore decimale. Le stringhe sono ordinate in base ai punti di codice Unicode e in base al maiuscolo / minuscolo. E così via.
user253751

11

Aggiungete spesso un punto di interruzione a un punto del codice ed eseguite l'app nel debugger per capire cosa sta succedendo? Se lo fai, questo è in gran parte perché non stai usando la trasparenza referenziale (RT) nei tuoi progetti. E quindi è necessario eseguire il codice per capire cosa fa.

L'intero punto di RT è che il codice è altamente deterministico, cioè puoi leggere il codice e capire cosa fa, ogni volta, per lo stesso set di input. Una volta che inizi ad aggiungere variabili mutanti, alcune delle quali hanno un ambito oltre una singola funzione, non puoi semplicemente leggere il codice. Tale codice deve essere eseguito, nella tua testa o nel debugger, per capire come funziona veramente.

Più è semplice leggere e ragionare il codice, più è semplice mantenere e individuare i bug, in modo da risparmiare tempo e denaro per te e il tuo datore di lavoro.


10
"Una volta che inizi ad aggiungere variabili mutanti, alcune delle quali hanno oltre una singola funzione, non puoi semplicemente leggere il codice, ma devi eseguirlo, nella tua testa o nel debugger, per capire come funziona veramente. ": Buon punto. In altre parole, la trasparenza referenziale non significa solo che un pezzo di codice produrrà sempre lo stesso risultato per gli stessi input, ma anche che il risultato prodotto è l' unico effetto di quel pezzo di codice, che non ci sono altri lati nascosti effetto come cambiare alcune variabili che sono state definite molto lontano in un altro modulo.
Giorgio,

Questo è un buon punto. Ho un po 'di problemi con il più semplice che il codice sia leggere / ragionare argomento, poiché più semplice da leggere o ragionare è un attributo del codice un po' vago e soggettivo.
Eyal Roth,

Una volta che inizi ad aggiungere variabili mutanti, alcune delle quali hanno un ambito oltre una singola funzione, ma perché l'operazione di assegnazione è scoraggiata anche quando l'ambito delle variabili è locale per funzionare?
rahulaga_dev,

9

La gente getta il termine "più facile da ragionare", ma non spiega mai cosa significhi. Considera il seguente esempio:

result1 = foo("bar", 12)
// 100 lines of code
result2 = foo("bar", 12)

Sono result1e result2uguali o diversi? Senza trasparenza referenziale, non ne hai idea. Devi effettivamente leggere il corpo di fooper assicurarti, e possibilmente il corpo di qualsiasi funzione foochiama, e così via.

Le persone non notano questo onere perché sono abituate, ma se vai a lavorare in un ambiente puramente funzionale per un mese o due, poi torni, lo sentirai, ed è un grosso problema .

Ci sono così tanti meccanismi di difesa che le persone fanno per aggirare la mancanza di trasparenza referenziale. Per il mio piccolo esempio, potrei voler rimanere result1in memoria, perché non saprei se cambierebbe. Quindi ho il codice con due stati: prima è result1stato memorizzato e dopo. Con la trasparenza referenziale, posso semplicemente ricalcolarlo facilmente, purché il ricalcolo non richieda tempo.


1
Hai detto che la trasparenza referenziale ti consente di ragionare sul risultato delle chiamate a foo () e di sapere se result1e result2sono le stesse. Un altro aspetto importante è che se foo("bar", 12)è referenzialmente trasparente, non è necessario chiedersi se questa chiamata ha prodotto effetti altrove (impostare alcune variabili, eliminare un file, qualunque cosa).
Giorgio,

L'unica "integrità referenziale" con cui ho familiarità riguarda i database relazionali.
Segna il

1
@Mark È un errore di battitura. Karl intendeva trasparenza referenziale, come è evidente dal resto della sua risposta.
Andres F.

6

Direi: la trasparenza referenziale non è buona solo per la programmazione funzionale ma per tutti coloro che lavorano con le funzioni perché segue il principio del minimo stupore.

Hai una funzione e puoi ragionare meglio su ciò che fa perché non ci sono fattori esterni che devi prendere in considerazione, per un dato input l'output sarà sempre lo stesso. Anche nel mio linguaggio imperativo cerco di seguire questo paradigma il più possibile, la cosa successiva che in pratica segue automaticamente da questo è: piccole funzioni facili da capire invece delle raccapriccianti funzioni della linea 1000+ in cui a volte corro.

Queste grandi funzioni fanno magie e ho paura di toccarle perché possono rompersi in modi spettacolari.

Quindi le funzioni pure non sono qualcosa solo per la programmazione funzionale, ma per ogni programma.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.