Cosa rende i linguaggi di programmazione funzionale dichiarativi rispetto a Imperativi?

17

In molti articoli, che descrivono i vantaggi della programmazione funzionale, ho visto linguaggi di programmazione funzionali, come Haskell, ML, Scala o Clojure, denominati "linguaggi dichiarativi" distinti dai linguaggi imperativi come C / C ++ / C # / Giava. La mia domanda è ciò che rende i linguaggi di programmazione funzionale dichiarativi rispetto all'imperativo.

Una spiegazione spesso incontrata che descrive le differenze tra la programmazione dichiarativa e quella imperativa è che nella programmazione imperativa si dice al computer "Come fare qualcosa" invece di "Cosa fare" nelle lingue dichiarative. Il problema che ho con questa spiegazione è che tu stai facendo costantemente entrambi in tutti i linguaggi di programmazione. Anche se si scende all'assemblaggio di livello più basso si sta ancora dicendo al computer "Cosa fare", si dice alla CPU di aggiungere due numeri, non si istruisce su come eseguire l'aggiunta. Se andiamo dall'altra parte dello spettro, un linguaggio funzionale puro di alto livello come Haskell, in realtà stai dicendo al computer come ottenere un compito specifico, cioè quello che il tuo programma è una sequenza di istruzioni per raggiungere un compito specifico che il computer non sa come ottenere da solo. Comprendo che linguaggi come Haskell, Clojure, ecc. Sono ovviamente di livello superiore a C / C ++ / C # / Java e offrono funzionalità come valutazione lazy, strutture dati immutabili, funzioni anonime, curry, strutture dati persistenti, ecc. programmazione funzionale possibile ed efficiente, ma non li classificherei come linguaggi dichiarativi.

Un puro linguaggio dichiarativo per me sarebbe uno che è interamente composto solo da dichiarazioni, un esempio di tali linguaggi sarebbe CSS (Sì, so che CSS non sarebbe tecnicamente un linguaggio di programmazione). Il CSS contiene solo dichiarazioni di stile che vengono utilizzate dall'HTML e Javascript della pagina. I CSS non possono fare altro che fare dichiarazioni, non possono creare funzioni di classe, cioè funzioni che determinano lo stile da visualizzare in base ad alcuni parametri, non è possibile eseguire script CSS, ecc. Che per me descrive un linguaggio dichiarativo (avviso non ho detto dichiarativo programmazione lingua).

Aggiornamento:

Recentemente ho giocato con Prolog e per me, Prolog è il linguaggio di programmazione più vicino a un linguaggio pienamente dichiarativo (almeno secondo me), se non è l'unico linguaggio di programmazione pienamente dichiarativo. Elaborare la programmazione in Prolog avviene facendo dichiarazioni che dichiarano un fatto (una funzione di predicato che restituisce true per un input specifico) o una regola (una funzione di predicato che restituisce true per una data condizione / modello in base agli input), regole sono definiti utilizzando una tecnica di abbinamento del modello. Per fare qualsiasi cosa in prolog, si interroga la knowledge base sostituendo uno o più input di un predicato con una variabile e prolog prova a trovare i valori per la variabile o le variabili per le quali il predicato ha successo.

Il mio punto è in prolog non ci sono istruzioni imperative, fondamentalmente stai dicendo (dichiarando) al computer quello che sa e poi chiedendo (interrogando) sulla conoscenza. Nei linguaggi di programmazione funzionale date ancora delle istruzioni, cioè prendete un valore, chiamate la funzione X e aggiungetene 1, ecc., Anche se non state manipolando direttamente le posizioni di memoria o scrivendo il calcolo passo dopo passo. Non direi che la programmazione in Haskell, ML, Scala o Clojure è dichiarativa in questo senso, anche se potrei sbagliarmi. È corretto, vero, puro programmatore funzionale dichiarativo nel senso che ho descritto sopra.

    
posta ALXGTV 15.05.2015 - 10:57
fonte

5 risposte

16

Sembra che tu stia traendo una linea tra dichiarare le cose e istruire una macchina. Non esiste una separazione così dura e veloce. Poiché la macchina che viene istruita nella programmazione imperativa non ha bisogno dell'hardware fisico, c'è molta libertà di interpretazione. Quasi tutto può essere visto come un programma esplicito per la giusta macchina astratta. Ad esempio, potresti vedere CSS come un linguaggio di alto livello per programmare una macchina che risolve principalmente selettori e imposta gli attributi degli oggetti DOM così selezionati.

La domanda è se una tale prospettiva sia ragionevole, e al contrario, quanto la sequenza di istruzioni assomigli a una dichiarazione del risultato che viene calcolata. Per i CSS, la prospettiva dichiarativa è chiaramente più utile. Per C, la prospettiva imperativa è chiaramente predominante. Per quanto riguarda le lingue come Haskell, beh ...

La lingua ha specificato, semantica concreta. Cioè, naturalmente si può interpretare il programma come una catena di operazioni. Non ci vuole nemmeno troppo sforzo per scegliere le operazioni primitive in modo che si adattino bene all'hardware di base (questo è ciò che fanno la macchina STG e altri modelli).

Tuttavia, il modo in cui i programmi di Haskell sono scritti, spesso possono essere letti come una descrizione del risultato da calcolare. Prendi, ad esempio, il programma per calcolare la somma dei primi fattori fattoriali N:

sum_of_fac n = sum [product [1..i] | i <- ..n]

Puoi desugar questo e leggerlo come una sequenza di operazioni STG, ma molto più naturale è leggerlo come una descrizione del risultato (che è, penso, una definizione più utile di programmazione dichiarativa di "what to compute"): il risultato è la somma dei prodotti di [1..i] per tutti i = 0, ..., n. E questo è molto più dichiarativo di quasi ogni programma o funzione C.

    
risposta data 15.05.2015 - 11:12
fonte
20

L'unità di base di un programma imperativo è la istruzione . Le dichiarazioni vengono eseguite per i loro effetti collaterali. Modificano lo stato che ricevono. Una sequenza di istruzioni è una sequenza di comandi, che denota fare questo e farlo. Il programmatore specifica l'ordine esatto per eseguire il calcolo. Questo è ciò che la gente intende dicendo dicendo al computer come di farlo.

L'unità di base di un programma dichiarativo è l'espressione . Le espressioni non hanno effetti collaterali. Specificano una relazione tra un input e un output, creando un output nuovo e separato dal loro input, piuttosto che modificando il loro stato di input. Una sequenza di espressioni non ha senso senza alcune espressioni contenenti che specificano la relazione tra loro. Il programmatore specifica le relazioni tra i dati e il programma deduce l'ordine per eseguire il calcolo da tali relazioni. Questo è ciò che la gente intende dicendo dicendo al computer cosa fare.

Le lingue imperative hanno espressioni, ma il loro principale mezzo per fare le cose è affermazioni. Allo stesso modo, i linguaggi dichiarativi hanno alcune espressioni con semantica simile alle istruzioni, come le sequenze monade all'interno della notazione% hashell di haskell, ma al loro centro sono una grande espressione. Ciò consente ai principianti di scrivere un codice che ha un aspetto molto imperativo, ma il vero potere del linguaggio arriva quando sfuggi a quel paradigma.

    
risposta data 15.05.2015 - 14:52
fonte
7

La vera caratteristica che separa la programmazione dichiarativa dalla programmazione imperativa è in uno stile dichiarativo che non stai dando istruzioni sequenziali; al livello più basso si, la CPU funziona in questo modo, ma questa è una preoccupazione del compilatore.

Suggerisci che il CSS sia un "linguaggio dichiarativo", non lo chiamerei affatto un linguaggio. È un formato di struttura dati, come JSON, XML, CSV o INI, solo un formato per la definizione di dati noti a un interprete di una certa natura.

Alcuni effetti collaterali interessanti si verificano quando si estrae l'operatore di assegnamento da una lingua, e questa è la vera causa della perdita di tutte quelle istruzioni imperative di step1-step2-step3 in linguaggi dichiarativi.

Che cosa fai con l'operatore di assegnazione in una funzione? Si creano passaggi intermedi. Questo è il punto cruciale, l'operatore di assegnazione viene utilizzato per modificare i dati in modalità passo-passo. Non appena non puoi più modificare i dati, perdi tutti questi passaggi e finisci con:

Ogni funzione ha una sola istruzione, questa dichiarazione è la singola dichiarazione

Ora ci sono molti modi per rendere una singola affermazione simile a molte affermazioni, ma questo è solo un trucco, ad esempio: 1 + 3 + (2*4) + 8 - (7 / (8*3)) questa è chiaramente una singola istruzione, ma se la scrivi come ...

1 +
3 +
(
  2*4
) +
8 - 
(
  7 / (8*3)
)

Può assumere l'aspetto di una sequenza di operazioni che può essere più facile per il cervello di riconoscere la decomposizione desiderata che l'autore intendeva. Lo faccio spesso in C # con codice come ..

people
  .Where(person => person.MiddleName != null)
  .Select(person => person.MiddleName)
  .Distinct();

Questa è una singola affermazione, ma mostra l'aspetto di una scomposizione in molti - nota che non ci sono assegnazioni.

Si noti inoltre, in entrambi i metodi precedenti: il modo in cui il codice viene effettivamente eseguito non è nell'ordine in cui lo si legge immediatamente; perché questo codice dice cosa fare, ma non impone come . Nota che la semplice aritmetica sopra riportata ovviamente non verrà eseguita nella sequenza scritta da sinistra a destra, e nell'esempio C # sopra questi 3 metodi sono in realtà tutti rientranti e non eseguiti in sequenza in sequenza, il compilatore di fatto genera codice questo farà ciò che l'istruzione vuole , ma non necessariamente come si assume.

Credo che abituarsi a questo approccio senza passaggi intermedi della codifica dichiarativa sia davvero la parte più difficile di tutto ciò; e perché Haskell è così complicato perché poche lingue lo disabilitano davvero come fa Haskell. Devi iniziare a fare qualche ginnastica interessante per realizzare alcune cose che normalmente farai con l'ausilio di variabili intermedie, come la sommatoria.

In un linguaggio dichiarativo, se vuoi una variabile intermedia con cui fare qualcosa, significa passarla come parametro a una funzione che fa quella cosa. Ecco perché la ricorsione diventa così importante.

sum xs = (head xs) + sum (tail xs)

Non puoi creare una variabile resultSum e aggiungerla mentre fai un loop su xs , devi semplicemente prendere il primo valore e aggiungerlo a qualunque sia la somma di tutto il resto - e accedere a tutto altrimenti devi passare xs a una funzione tail perché non puoi semplicemente creare una variabile per xs e far scoppiare la testa che sarebbe un approccio imperativo. (Sì, so che potresti usare la destrutturazione ma questo esempio deve essere illustrativo)

    
risposta data 15.05.2015 - 20:11
fonte
6

So di essere in ritardo per la festa, ma ho avuto un'illuminazione l'altro giorno, quindi eccoci qui ...

Penso che i commenti su funzionali, immutabili e senza effetti collaterali mancano il bersaglio quando si spiega la differenza tra dichiarativo vs imperativo o che cosa significa programmazione dichiarativa. Inoltre, come hai detto nella tua domanda, l'intero "cosa fare" e "come farlo" è troppo vago e in realtà non lo spiega neanche.

Prendiamo come base il semplice codice a = b + c e visualizziamo l'istruzione in alcune lingue diverse per ottenere l'idea:

Quando scriviamo a = b + c in un linguaggio imperativo, come C, stiamo assegnando il valore corrente di b + c alla variabile a e niente di più. Non stiamo facendo alcuna dichiarazione fondamentale su cosa sia a . Piuttosto, stiamo semplicemente eseguendo un passaggio in un processo.

Quando scriviamo a = b + c in un linguaggio dichiarativo, come Microsoft Excel, (sì, Excel è un linguaggio di programmazione e probabilmente il più dichiarativo di tutti), stiamo affermando un relazione tra a , b e c tali che è sempre il caso che a sia la somma degli altri due. Non è un passaggio di un processo, è un'invarianza, una garanzia, una dichiarazione di verità.

Anche le lingue funzionali sono dichiarative, ma quasi per caso. Ad esempio in Haskel a = b + c asserisce anche una relazione invariante, ma solo perché b e c sono immutabili.

Quindi sì, quando gli oggetti sono immutabili e le funzioni sono prive di effetti collaterali, il codice diventa dichiarativo (anche se sembra identico al codice imperativo), ma non lo sono. Né evita il compito. Il punto del codice dichiarativo è quello di fare affermazioni fondamentali sulle relazioni.

    
risposta data 17.06.2016 - 03:23
fonte
0

Hai ragione che non esiste una chiara distinzione tra dire al computer cosa fare e come farlo

.

Tuttavia, da un lato dello spettro si pensa quasi esclusivamente a come manipolare la memoria. Cioè, per risolvere un problema, lo presenti al computer in una forma come "imposta questa posizione di memoria su x, quindi imposta quella posizione di memoria su y, passa alla posizione di memoria z ..." e poi in qualche modo finisci per avere il risultato in qualche altra posizione di memoria.

Nei linguaggi gestiti come Java, C # e così via, non si ha più accesso diretto alla memoria hardware. Il programmatore imperativo si occupa ora di variabili statiche, riferimenti o campi di istanze di classe, che sono tutte in qualche misura assenti per le posizioni di memoria.

Nelle langughe come Haskell, OTOH, la memoria è completamente scomparsa. Semplicemente non è il caso in

f a b = let y = sum [a..b] in y*y

ci devono essere due celle di memoria che contengono gli arguenti aeb e un'altra che contiene il risultato intermedio y. Per essere sicuri, un backend del compilatore potrebbe emettere il codice finale che funziona in questo modo (e in un certo senso deve farlo finché l'architettura di destinazione è una macchina v. Neumann).

Ma il punto è che non abbiamo bisogno di interiorizzare la v. architettura Neumann per capire la funzione di cui sopra. Né abbiamo bisogno di un computer contemporaneo per eseguirlo. Sarebbe facile tradurre programmi in un puro linguaggio FP in una macchina ipotetica che funziona, ad esempio, sulla base del calcolo dello SKI. Ora prova lo stesso con un programma C!

A pure declarative languages for me would be one which is comprised entirely of declarations only

Questo non è abbastanza strong, IMHO. Anche un programma C è solo una sequenza di dichiarazioni. Sento che dobbiamo qualificare ulteriormente le dichiarazioni. Ad esempio, ci stanno dicendo che cosa è (dichiarativo) o cosa fa (imperativo).

    
risposta data 15.05.2015 - 14:32
fonte