Se possiamo fare la programmazione funzionale con Python, abbiamo bisogno di un linguaggio di programmazione funzionale specifico? [chiuso]


22

Usando generatori e lambda, possiamo fare una programmazione funzionale con Python. Puoi anche ottenere la stessa cosa con Ruby.

Quindi la domanda è: perché abbiamo bisogno di specifici linguaggi di programmazione funzionale come Erlang, Haskell e Scheme? C'è qualcosa di diverso che forniscono questi specifici linguaggi di programmazione funzionale? Perché non possiamo semplicemente usare Python per la programmazione funzionale?


30
tutti quelli erano in giro anche prima della creazione di Python
Mahmoud Hossam,

51
Una Ford Pinto è un'auto. Perché abbiamo bisogno di macchine veloci specifiche come Ferrari?
Martin York,

11
Usando classi e template, possiamo fare qualsiasi cosa OO in C ++. Perché Java e Python sono mai stati creati? Cosa aggiungono?
9000

19
Tutti i linguaggi di programmazione (senza alcuni linguaggi di ricerca puramente accademici) sono equivalenti a Turing, quindi ciò che puoi fare nella lingua A puoi farlo in qualsiasi altra lingua. Quindi, seguendo questo treno di pensieri, abbiamo solo bisogno di un linguaggio completo di Turing - diciamo come sendmail.cf;) okmij.org/ftp/Computation/#sendmail-Turing
Maglob

17
Se conoscessi qualcuno di questi linguaggi, non affermeresti che Python funzioni bene nella programmazione funzionale. Non Lo fa abbastanza bene da incorporare una parte delle cose FP-ish, ma non meglio.

Risposte:


20

Apprezzo la domanda, perché personalmente sono un grande fan di Python e dello stile di programmazione funzionale. Ho una lunga esperienza in Python e ho iniziato a studiare Haskell di recente, quindi ecco alcuni punti basati sulle mie esperienze personali sulle differenze tra queste lingue, dal punto di vista funzionale.

Purezza

Anche se non ti interessa la purezza delle funzioni (cioè non avere effetti collaterali) come un principio, ha un effetto pratico su quanto sia facile leggere il codice e ragionarne. Anche se mantieni la purezza nelle tue funzioni Python, c'è una grande differenza nel fatto che il compilatore imponga la purezza e, soprattutto, che la libreria standard sia basata su purezza e strutture di dati immutabili.

Prestazione

Potresti interessarti o meno delle prestazioni a seconda del dominio dell'applicazione, ma la tipizzazione statica e la purezza garantita offrono al compilatore molto di più su cui lavorare, rispetto a Python e altri linguaggi dinamici (anche se devo ammettere che PyPy sta andando alla grande inroads, e ad esempio LuaJIT è al limite del miracoloso).

Ottimizzazione delle chiamate di coda

Relativo alle prestazioni, ma leggermente diverso. Anche se non ti preoccupi troppo delle prestazioni di runtime, non avere l'ottimizzazione della coda (specialmente per la ricorsione della coda) limita il modo in cui puoi implementare algoritmi in Python senza colpire i limiti dello stack.

Sintassi

Questo è il motivo principale per cui ho iniziato a esaminare linguaggi funzionali "reali" invece di usare semplicemente Python con stile funzionale. Anche se penso che Python abbia una sintassi molto espressiva in generale, ha alcuni punti deboli specifici della codifica funzionale. Per esempio:

  • La sintassi per le funzioni lambda è piuttosto dettagliata e limitata in ciò che possono contenere
  • Nessuno zucchero sintattico per composizione funzionale cioè f = g . hvs.f = lambda *arg: g(h(*arg))
  • Nessuno zucchero sintattico per applicazione parziale, cioè f = map gvs.f = functools.partial(map, g)
  • Nessuno zucchero sintattico per l'utilizzo di operatori infix in funzioni di ordine superiore, ovvero sum = reduce (+) lstvs.sum = reduce(operator.add, lst)
  • Nessuna corrispondenza dei pattern o protezioni per gli argomenti delle funzioni, che facilitano l'espressione delle condizioni finali di ricorsione e alcuni casi limite con una sintassi molto leggibile.
  • Le parentesi non sono mai opzionali per le chiamate di funzione e non esiste zucchero sintattico per le chiamate nidificate. Immagino che questa sia una questione di gusti, ma soprattutto nel codice funzionale, trovo che sia comune alle chiamate di funzioni a catena e trovo y = func1 $ func2 $ func3 xpiù facile da leggere rispetto a y = func1(func2(func3(x))), una volta che hai familiarità con quella notazione.

28

Queste sono le differenze più importanti:

Haskell

  • Valutazione pigra
  • Compila per codice macchina
  • La digitazione statica garantisce che le funzioni siano pure
  • Tipo di inferenza

Haskell ed Erlang

  • Corrispondenza del modello

Erlang

  • Modello di attore di concorrenza, processi leggeri

schema

  • Macro

Tutte le lingue

  • chiusure reali (il rubino ha chiusure, se si può discutere di pitone, vedere i commenti)
  • una libreria standard adatta per uno stile di programmazione funzionale (raccolte immutabili, mappa, filtro, piega ecc.)
  • ricorsione della coda (questo può essere trovato anche in alcuni linguaggi non funzionali)

Inoltre, dovresti dare un'occhiata ai linguaggi della famiglia ML come SML, Ocaml e F # e Scala, che fonde OO e la programmazione funzionale in un modo nuovo. Tutte queste lingue hanno caratteristiche interessanti uniche.


3
+1 buon post. È possibile aggiungere l'inferenza di tipo sui processi Haskell e Lightweight su Erlang.
Jonas,

1
Python ha mappa, filtro e piega (riduci). Riguardo alle "chiusure reali": se definisci una chiusura reale come una chiusura che può contenere qualcosa di diverso da una singola espressione, Haskell non ha nemmeno chiusure reali (ma ovviamente Haskell ha poche cose che non sono espressioni ...) . Tuttavia, il punto sulle strutture di dati immutabili è buono, quindi +1 per quello. Inoltre: ricorsione della coda (e in genere ricorsione meno costosa).
sepp2k,

2
+1 per "la digitazione statica assicura che le funzioni siano pure". È molto bello avere un sistema di tipi che discrimina tra funzioni pure e non pure. (Conteggio dei punti costanti del C ++. :)
Macke,

1
@btilly: non la considero una chiusura se non puoi assegnarla a una variabile che rientra nell'ambito. Altrimenti dovremmo dire che anche Java ha delle chiusure, dato che puoi usare lo stesso trucco lì.
Kim,

3
In una chiusura posso accedere a una variabile come faccio normalmente. Questo è vero per le chiusure di Haskell e Ruby, ma non per i poveri sostituti di Python o Java. Forse qualcun altro può illuminarci di Erlang, non lo so bene.
Kim,

19

È difficile definire esattamente cosa sia un "linguaggio funzionale": al di fuori delle lingue che hai elencato, solo Haskell è puramente funzionale (tutti gli altri adottano una sorta di approccio ibrido). Ci sono alcune caratteristiche del linguaggio che sono molto utili per la programmazione funzionale, e Ruby e Python non ne hanno abbastanza per essere ottimi ambienti per FP. Ecco la mia lista di controllo personale, in ordine di importanza:

  1. Funzioni e chiusure di prima classe (Ruby, Python e tutti gli altri che hai elencato hanno questo).
  2. Ottimizzazione di coda chiamata garantita (Erlang, Haskell, Scala e Scheme hanno questo, ma non Python, Ruby o Clojure (ancora)).
  3. Supporto per l' immutabilità nel linguaggio e nelle librerie standard (questo è un grande che tutti i "linguaggi funzionali" che hai elencato (tranne Scheme) ma Ruby e Python no).
  4. Supporto a livello di linguaggio per funzioni referenzialmente trasparenti (o pure) (per quanto ne so, solo Haskell lo possiede attualmente).

La necessità di (1) dovrebbe essere ovvia: le funzioni di ordine superiore sono estremamente difficili senza funzioni di prima classe. Quando le persone parlano di Ruby e Python come buone lingue per FP, di solito ne parlano. Tuttavia, questa caratteristica particolare è necessaria ma non sufficiente per rendere un linguaggio buono per FP.

(2) è stata una necessità tradizionale per FP sin da quando è stato inventato Scheme. Senza TCO, è impossibile programmare con una ricorsione profonda, che è uno dei cardini di FP, perché si ottengono overflow dello stack. L'unico linguaggio "funzionale" (per definizione popolare) che non ha questo è Clojure (a causa delle limitazioni della JVM), ma Clojure ha una varietà di hack per simulare il TCO. (Cordiali saluti, Ruby TCO è specifico dell'implementazione , ma Python specificamente non lo supporta .) Il motivo per cui il TCO deve essere garantito è che se si tratta di specifiche implementazioni, le funzioni ricorsive profonde si romperanno con alcune implementazioni, quindi non si può davvero usali affatto.

(3) è un'altra cosa importante che i linguaggi funzionali moderni (in particolare Haskell, Erlang, Clojure e Scala) hanno che Ruby e Python non hanno. Senza entrare troppo nei dettagli, l'immutabilità garantita elimina intere classi di bug, specialmente in situazioni simultanee, e consente cose pulite come strutture di dati persistenti . È molto difficile sfruttare questi vantaggi senza supporto a livello linguistico.

(4) è, per me, la cosa più interessante sui linguaggi puramente funzionali (al contrario dei linguaggi ibridi). Considera la seguente funzione Ruby estremamente semplice:

def add(a, b)
  a + b
end

Sembra una funzione pura, ma a causa del sovraccarico dell'operatore, potrebbe mutare entrambi i parametri o causare effetti collaterali come la stampa sulla console. È improbabile che qualcuno sovraccarichi l' +operatore per avere un effetto collaterale, ma il linguaggio non offre garanzie. (Lo stesso vale per Python, anche se forse non con questo esempio specifico.)

In un linguaggio puramente funzionale, d'altra parte, ci sono garanzie a livello di linguaggio che le funzioni sono referenzialmente trasparenti. Ciò presenta numerosi vantaggi: le funzioni pure possono essere facilmente memorizzate; possono essere facilmente testati senza fare affidamento su alcun tipo di stato globale; e i valori all'interno della funzione possono essere valutati pigramente o in parallelo senza preoccuparsi di problemi di concorrenza. Haskell ne approfitta appieno, ma non ne so abbastanza di altri linguaggi funzionali per sapere se lo fanno.

Detto questo, è possibile utilizzare le tecniche FP in quasi tutte le lingue (anche Java). Ad esempio, MapReduce di Google è ispirato da idee funzionali, ma per quanto ne so non usano linguaggi "funzionali" per i loro grandi progetti (penso che utilizzino principalmente C ++, Java e Python).


2
+1 per la spiegazione approfondita - anche io come outsider del FP l'ho capito. Grazie! :-)
Péter Török,

la risposta più preziosa a questa domanda finora. Basato su fatti e molte informazioni. Ottimo lavoro.
Wirrbel,

Scala ha la ricorsione della coda e, come con Scheme, viene eseguita automaticamente se una chiamata ricorsiva è in posizione di coda (a differenza di Clojure dove deve essere esplicitamente richiesta). C'è anche un'annotazione in modo da poter verificare che il compilatore si genererà il codice ricorsivo coda. Ciò che non ha è il TCO più generalizzato di Scheme. Presumo che tu lo sappia, ma dal momento che hai approfondito molti dettagli su molte altre cose, mi è sembrato uno strano licenziamento / omissione.
itsbruce,

@itsbruce Questo post è piuttosto vecchio, IIRC Scala non ce l'aveva al momento (o potrei essermi appena sbagliato;). Aggiornato.
shosti,

Non ho usato Scala sin dall'inizio ma ha avuto una ricorsione della coda nel 2008, quando mi stavo interessando ;-) Mi riferisco alle persone che pongono questo argomento a questa particolare domanda SO perché ha delle buone risposte e ho solo notato quella stranezza, così commentato per completezza.
itsbruce,

11

Le lingue che menzioni sono molto diverse.

Mentre Python e Ruby sono linguaggi tipizzati dinamicamente, Haskell è tipizzato staticamente. Erlang è un linguaggio concorrente e utilizza il modello Actor ed è molto diverso da tutte le altre lingue che menzioni.

Python e Ruby hanno molti costrutti imperativi mentre in un linguaggio funzionale più puro come Haskell, tutto restituisce qualcosa o in altre parole tutto è una funzione.


@kRON: Bene, il sistema dei tipi è una proprietà importante di un linguaggio e ha chiesto "C'è qualcosa di diverso che questi specifici linguaggi di programmazione funzionale forniscono?". Certo, puoi usare il modello Actor con altre lingue ma Erlang lo ha incorporato nella lingua. Erlang utilizza processi leggeri e ha costrutti di linguaggio integrati per la programmazione distribuita, quindi un linguaggio "concorrente".
Jonas,

8

In ritardo alla festa come al solito, ma dirò comunque le cose.

Un linguaggio di programmazione funzionale non è un linguaggio che consente la programmazione funzionale. Se dovessimo seguire questa definizione, praticamente qualsiasi lingua ovunque sia un linguaggio di programmazione funzionale. (Lo stesso vale per OOP, per inciso. Puoi scrivere in uno stile OOP in C se vuoi. Pertanto, secondo la tua logica, C è un linguaggio OOP.)

Ciò che rende un linguaggio di programmazione funzionale non è quello ti consente di programmare, è ciò che ti consente di programmare facilmente . Questa è la chiave.

Quindi, Python ha lambda (che sono affari incredibilmente simili alla chiusura) e ti offre un paio di funzioni di libreria che vedrai nelle librerie funzionali come "map" e "fold". Questo non è abbastanza per renderlo un linguaggio di programmazione funzionale, tuttavia, poiché è difficile da programmare in modo coerente in uno stile funzionale adeguato (e il linguaggio certamente non impone questo stile!). Al suo centro Python è un linguaggio imperativo che si occupa delle operazioni di manipolazione dello stato e dello stato e questo è semplicemente in contrasto con la semantica della valutazione dell'espressione e dell'espressione di un linguaggio funzionale.

Quindi perché abbiamo linguaggi di programmazione funzionale quando Python (o Ruby (o inserisci il linguaggio che preferisci)) può "eseguire la programmazione funzionale"? Poiché Python, et al. Non possono eseguire una corretta programmazione funzionale. Ecco perchè.


6

È possibile eseguire la programmazione funzionale in Java (vedere ad esempio http://functionaljava.org/ ). È anche possibile fare programmazione orientata agli oggetti in C . Non è poi così idiomatico.

Quindi in effetti non abbiamo assolutamente bisogno di Erlang, Haskell, Scheme o di alcun linguaggio di programmazione specifico, ma rappresentano tutti approcci e compromessi diversi, rendendo alcuni compiti più facili e altri più difficili. Quello che dovresti usare dipende da cosa vuoi ottenere.


4

Questa domanda può essere applicata a un numero infinito di lingue e paradigmi.

  • Dato che tutti usano il C ++, perché abbiamo bisogno di altri linguaggi generici?
  • Poiché java è un linguaggio OO così eccezionale, perché esistono altre lingue OO?
  • Dato che perl è un linguaggio di scripting straordinario, perché abbiamo bisogno di Python?
  • Yatta, yatta, yatta

La maggior parte, se non tutte le lingue, esiste per un motivo specifico. Esistono perché qualcuno aveva un'esigenza che nessuna lingua attuale ha riempito o riempito male. (Questo ovviamente non si applica a tutte le lingue, ma penso che si applichi alla maggior parte delle lingue conosciute.) Ad esempio, Python è stato originariamente sviluppato per interfacciarsi con il sistema operativo Amoeba [ 1 , 2 ] ed Erlang è stato creato per aiutare nello sviluppo di applicazioni di telefonia [ 3 ]. Quindi una risposta alla domanda "Perché abbiamo bisogno di un altro linguaggio funzionale?" può semplicemente essere, perché [inserire-il-nome-di-qualcuno-che-sa-come-progettare-i-linguaggi] non piaceva come lo faceva Python.

Questo riassume praticamente ciò che penso sia la risposta. Mentre puoi fare qualsiasi cosa con Python che puoi fare con un linguaggio funzionale, vorresti davvero farlo? Tutto quello che puoi fare in C, puoi farlo in assemblea, ma vorresti farlo? Lingue diverse saranno sempre le migliori nel fare cose diverse, ed è così che dovrebbe essere.


2

La programmazione funzionale riguarda tanto il paradigma del design quanto le caratteristiche del linguaggio specifico. Oppure, in altre parole, lambda e una funzione di mappa non fanno un linguaggio di programmazione funzionale. Python e Ruby hanno alcune caratteristiche ispirate ai linguaggi di programmazione funzionale, ma generalmente si scrive codice in modo imperativo. (È un po 'come C: puoi scrivere codice simile a OO in C, ma nessuno considera seriamente C come un linguaggio OO.)

Guarda: la programmazione funzionale non riguarda solo lambda, o map, o funzioni di ordine superiore. Si tratta di design . Un programma scritto in un "vero" linguaggio di programmazione funzionale risolve i problemi attraverso la composizione delle funzioni. Mentre i programmi scritti in Ruby o Python possono utilizzare funzionalità simili a FP, in genere non leggono come un insieme di funzioni composte.


0

Ogni linguaggio funzionale che hai citato si adatta abbastanza bene a una certa nicchia e gli schemi idiomatici che ognuno incoraggia li rende molto adatti a determinati compiti che sarebbe impossibile da svolgere in Python se non avessi creato una vasta libreria di moduli helper. Il più ovvio di una di queste eccellenze di nicchia è il modello di concorrenza di Erlang. Gli altri hanno anche punti di forza simili.


0

Ogni concetto disponibile in lambda-calculus, LISP e Scheme è disponibile in Python, quindi, sì, puoi fare una programmazione funzionale al suo interno. Se è conveniente o no è una questione di contesto e gusto.

Puoi facilmente scrivere un interprete LISP (o altro linguaggio funzionale) in Python (Ruby, Scala) (cosa significa?). Puoi scrivere un interprete per Python usando puramente funzionale, ma ci vorrebbe molto lavoro. Anche i linguaggi "funzionali" sono al giorno d'oggi multi-paradigma.

Questo vecchio e bellissimo libro ha la maggior parte (anche se non tutte) le risposte su quale sia l'essenza della programmazione funzionale.


@Arkaaito Ma sei d'accordo sul fatto che ogni concetto disponibile nel calcolo lambda, LISP e Scheme sia disponibile in Python?
Segna C

Sei sicuro che ogni concetto di lisp sia disponibile in Python? Macro, il codice è dati?
Logica SK

@ SK-logic Le macro non sono in lambda-calcolo, ma sì, sono disponibili in Python, sebbene non con quel nome. Python fornisce una eval()funzione, che è necessaria per il codice sono dati , ma va oltre: ti consente di modificare la maggior parte dell'ambiente di runtime, come in LISP.
Apalala,

@Apalala, i linguaggi dinamici eval()è una metaprogrammazione di runtime. A volte è utile, ma è estremamente costoso. Le macro di Lisp sono diverse, è una metaprogrammazione del tempo di compilazione. Non è disponibile in Python in alcuna forma utilizzabile.
Logica SK

@ SK-logic Questo tipo di macro infrange la premessa code-is-data , poiché i programmi non possono cambiare le macro (o nemmeno conoscerne l'esistenza). In Python, i programmi hanno accesso ai propri alberi di analisi in fase di esecuzione, se ne hanno bisogno. Le macro (pre-elaborazione statica) non funzionano affatto.
Apalala,

-5

Perché Python può anche programmare in uno stile non funzionale, e questo non è abbastanza buono per il purista di FP. Codificatori più pragmatici, tuttavia, possono godere dei benefici dello stile funzionale senza essere dogmatici al riguardo:

“Il programmatore funzionale suona piuttosto come un monaco medievale, negando a se stesso i piaceri della vita nella speranza che lo renda virtuoso. Per coloro che sono più interessati ai benefici materiali, questi "vantaggi" non sono molto convincenti. I programmatori funzionali sostengono che ci sono grandi vantaggi materiali ... [ma] questo è chiaramente ridicolo. Se l'omissione delle dichiarazioni di assegnazione avesse portato enormi benefici, i programmatori FORTRAN lo avrebbero fatto per vent'anni. È un'impossibilità logica di rendere più potente un linguaggio omettendo le funzionalità, indipendentemente da quanto possano essere brutte. "

- John Hughes, Perché la programmazione funzionale conta


6
Sono d'accordo. Qualsiasi lingua senza gotoè terribile.
Anon.

2
@Anon: O il suo grande fratello, call-with-current-continuation.
Chris Jester-Young,

4
Mason, il documento che stai citando sta dicendo l'esatto contrario. La tua citazione è solo un frammento di un paio di paragrafi di un articolo che racconta una storia diversa. In effetti, se citassi entrambi i paragrafi nel loro insieme, essi mostrerebbero chiaramente un altro significato.
Marco Mustapic,

2
@ Mason: sì, l'autore afferma che la modularizzazione e la valutazione pigra sono i veri vantaggi. Ma nota come la tua citazione originale fosse fuori contesto - sembra che tu suggerisca all'autore di rivendicare qualcosa contro FP, quando in realtà hai citato un paragrafo di un articolo con la conclusione che FP è eccezionale, solo per la ragione fuorviante di " meno è meglio". Il titolo dell'articolo mostra chiaramente l'intento dell'autore: "perché la programmazione funzionale conta" ;-) Certamente non supporta la tua affermazione che le lingue più pure di FP "sono un fastidio".
Andres F.

6
@Mason Wheeler: le lingue ibride precedono Python di un allungamento allungato. Lisp, per esempio, risale al 1959 ed è, in effetti, un linguaggio multi-paradigma. Supporta pienamente approcci funzionali, approcci procedurali e approcci alla programmazione orientati agli oggetti. Con i giusti pacchetti macro in cima puoi anche fare la programmazione logica abbastanza facilmente. Anche lo schema precede Python (e questo documento). Risale al 1975. Forse prima o poi dovresti dare un'occhiata a questa sequenza temporale di linguaggi di programmazione .
SOLO IL MIO OPINIONE corretta,
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.