La programmazione funzionale aumenta il "divario rappresentativo" tra problemi e soluzioni? [chiuso]


18

Poiché il linguaggio macchina (ad es. 0110101000110101) , I linguaggi dei computer si sono generalmente evoluti in forme più elevate di astrazione, rendendo generalmente più semplice la comprensione del codice quando viene applicato a un problema. L'assemblatore era un'astrazione sul codice della macchina, C era un'astrazione sull'assemblatore, ecc.

Il design orientato agli oggetti sembra essere molto bravo nel permetterci di modellare un problema in termini di oggetti, ad esempio, il problema di un sistema di registrazione dei corsi universitari può essere modellato con una Courseclasse, una Studentclasse, ecc. Quindi, quando scriviamo la soluzione in un linguaggio OO, abbiamo classi simili che assumono responsabilità e che generalmente sono utili per la progettazione, specialmente per la modularizzazione del codice. Se do questo problema a 10 team indipendenti che lo risolvono con un metodo OO, in genere le 10 soluzioni avranno le classi relative al problema in comune. Ci possono essere molte differenze quando inizi a entrare nell'accoppiamento e nelle interazioni di quelle classi, quindi non esiste nulla come "zero gap rappresentazionale".

La mia esperienza con la programmazione funzionale è molto limitata (nessun utilizzo nel mondo reale, solo programmi di tipo Hello World). Non riesco a vedere come tali lingue consentano di mappare facilmente le soluzioni FP ai problemi (con un basso gap rappresentativo) come fanno le lingue OO.

Comprendo i vantaggi di FP rispetto alla programmazione concorrente. Ma mi sto perdendo qualcosa o FP non sta riducendo il gap rappresentativo (rendendo le soluzioni più facili da capire)?

Un altro modo per chiederlo: il codice FP di 10 team diversi che risolvono lo stesso problema del mondo reale avrebbe molto in comune?


Da Wikipedia on Abstraction (informatica) (sottolineatura mia):

I linguaggi di programmazione funzionale mostrano comunemente astrazioni legate a funzioni , come le astrazioni lambda (trasformare un termine in una funzione di una variabile), funzioni di ordine superiore (i parametri sono funzioni), astrazione di parentesi (trasformare un termine in una funzione di una variabile).

Il divario rappresentazionale potrebbe essere potenzialmente aumentato, perché [alcuni] problemi del mondo reale non sono facilmente modellabili con tali astrazioni.


Un altro modo in cui vedo diminuire il divario rappresentativo è nel rintracciare gli elementi della soluzione al problema. Il 0s e' 1s in codice macchina è molto difficile da risalire, mentre la Studentclasse è facile tornare traccia. Non tutte le classi OO tracciano facilmente nello spazio del problema, ma molti lo fanno.

Le astrazioni FP non devono sempre essere spiegate per scoprire quale parte dello spazio problematico stanno risolvendo (a parte i problemi matematici )?OK - Sto bene in questa parte. Dopo aver esaminato molti altri esempi, vedo come le astrazioni di FP siano molto chiare per parti del problema che sono espresse nell'elaborazione dei dati.


La risposta accettata a una domanda correlata È possibile utilizzare UML per modellare un programma funzionale? - dice "I programmatori funzionali non hanno molto da usare per i diagrammi". Non mi interessa davvero se si tratta di UML, ma mi chiedo se le astrazioni di FP siano facili da capire / comunicare, se non ci sono diagrammi ampiamente utilizzati (supponendo che questa risposta sia corretta). Ancora una volta, il mio livello di utilizzo / comprensione del FP è banale, quindi non capisco la necessità di diagrammi su semplici programmi FP.

Il design OO ha funzioni / classe / livelli di pacchetto di astrazione, con incapsulamento (controllo degli accessi, occultamento delle informazioni) a ciascuno, che semplifica la gestione della complessità. Questi sono elementi che consentono di passare dal problema alla soluzione e viceversa.


Molte risposte parlano di come l'analisi e la progettazione sono fatte in FP in un modo analogo a OO, ma nessuno cita nulla di alto livello finora (Paul ha citato alcune cose interessanti, ma è di basso livello). Ieri ho fatto molte ricerche su Google e ho trovato alcune discussioni interessanti. Quanto segue è tratto da Refactoring Functional Programs di Simon Thompson (2004) (sottolineatura mia)

Nel progettare un sistema orientato agli oggetti, è scontato che il progetto precederà la programmazione. I progetti verranno scritti utilizzando un sistema come UML supportato da strumenti come Eclipse. I programmatori principianti possono imparare un approccio alla progettazione visiva usando sistemi come BlueJ. Il lavoro su una metodologia simile per la programmazione funzionale è riportato in FAD: Functional Analysis and Design , ma esiste poco altro lavoro. Potrebbero esserci diverse ragioni per questo.

  • I programmi funzionali esistenti hanno una scala che non richiede progettazione. Molti programmi funzionali sono piccoli, ma altri, come il compilatore Haskell di Glasgow, sono sostanziali.

  • I programmi funzionali modellano direttamente il dominio dell'applicazione, rendendo in tal modo irrilevante il design. Mentre i linguaggi funzionali forniscono una varietà di potenti astrazioni, è difficile sostenere che questi forniscano tutte e solo le astrazioni necessarie per modellare il mondo reale.

  • I programmi funzionali sono costruiti come una serie in evoluzione di prototipi.

Nella tesi di dottorato sopra citata , i vantaggi dell'utilizzo di Metodologie di analisi e progettazione (ADM) sono delineati indipendentemente dai paradigmi. Ma si sostiene che gli ADM dovrebbero allinearsi al paradigma di implementazione. Cioè, OOADM funziona meglio per la programmazione OO e non è ben applicato a un altro paradigma come FP. Ecco una grande citazione che penso parafrasasse quello che chiamo gap rappresentativo:

si può discutere a lungo su quale paradigma fornisce il miglior supporto per lo sviluppo del software, ma si ottiene il pacchetto di sviluppo più naturale, efficiente ed efficace quando si rimane all'interno di un singolo paradigma dalla descrizione del problema fino all'implementazione e alla consegna.

Ecco la serie di diagrammi proposti da FAD:

  • diagrammi di dipendenza delle funzioni che presentano una funzione con quelli che utilizza nella sua implementazione;
  • diagramma di dipendenza del tipo che fornisce lo stesso servizio per i tipi; e,
  • diagrammi di dipendenza del modulo che presentano viste dell'architettura del modulo del sistema.

C'è un caso di studio nella sezione 5.1 della tesi FAD, che è un sistema per automatizzare la produzione di dati relativi a un campionato di calcio (calcio). I requisiti sono funzionali al 100%, ad es. Immissione di risultati calcistici, produzione di classifiche, tabelle dei punteggi, tabelle delle presenze, trasferimento dei giocatori tra squadre, aggiornamento dei dati dopo nuovi risultati, ecc. , oltre a dichiarare che "le nuove funzionalità dovrebbero essere consentite a un costo minimo", qualcosa che è quasi impossibile da testare.

Purtroppo, a parte FAD, non vedo riferimenti moderni per i linguaggi di modellazione (visivi) proposti per FP. UML è un altro paradigma, quindi dovremmo dimenticarlo.


1
I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
maple_shaft

Risposte:


20

I dati di base sono strutturati allo stesso modo in quasi tutti i paradigmi. Avrai a Student, a Course, ecc., Che si tratti di un oggetto, una struttura, un disco o altro. La differenza con OOP non è come sono strutturati i dati, è come sono strutturate le funzioni.

In realtà trovo che i programmi funzionali siano molto più simili a come penso a un problema. Ad esempio, per pianificare il programma di uno studente per il prossimo semestre, pensi a cose come elenchi di corsi che uno studente ha completato, corsi in un corso di laurea, corsi offerti in questo semestre, corsi a cui uno studente ha completato i prerequisiti, corsi con orari che conflitto, ecc.

Improvvisamente, non è così chiaro quale classe dovrebbe creare e archiviare tutti questi elenchi. Ancor meno quando si hanno combinazioni complesse di questi elenchi. Tuttavia, devi scegliere una classe.

In FP, scrivi funzioni che accettano uno studente e un elenco di corsi e restituiscono un elenco filtrato di corsi. È possibile raggruppare tutte queste funzioni in un modulo. Non è necessario abbinarlo a una classe o all'altra.

Quindi i tuoi modelli di dati finiscono per assomigliare di più a come i programmatori OOP pensano ai loro modelli, prima che vengano inquinati con classi che non hanno altro scopo se non quello di fornire spazi convenienti per mettere funzioni che operano su combinazioni di altre classi. Nessuna lezione strana CourseStudentFilterListo simile di cui hai sempre bisogno in OOP, ma non pensare mai al progetto iniziale.


5
@BenAaronson il problema nella mia esperienza è che quindi qualsiasi funzione relativa a uno studente viene aggiunta alla classe Student e le sue responsabilità crescono e crescono fino a quando non è necessario introdurre StudentCourseFiltererper mantenere le cose gestibili
AlexFoxGill

3
Gli approcci @Den Hybrid hanno i loro meriti. Non è vero, tuttavia, che la distinzione sia artificiale. Ci sono molti compromessi che Scala ha dovuto fare per adattarsi a OOP e FP (l'inferenza di tipo meno potente è una. La complessità è un'altra: sì, un linguaggio che mescola OOP e FP tende ad essere più complesso - come in "difficile da usare e capire "- di uno dei suoi fratelli più puri ai due estremi).
Andres F.

3
@Den Vedi anche la programmazione "Mostly Functional" di Erik Meijer non funziona " , che si oppone all'approccio ibrido e per andare a pieno regime" fondamentalista ".
Andres F.

4
@Den La popolarità che descrivi è, temo, un incidente della storia. Uso Scala al lavoro ed è una bestia complessa per il modo in cui tenta di mescolare FP e OOP. Passare a un vero linguaggio FP come Haskell sembra una boccata d'aria fresca - e non sto nemmeno parlando da una prospettiva accademica!
Andres F.

3
@Den Non riesco a dire cosa faccia una di queste funzioni. Tuttavia, posso dirti se la prima funzione in FP era un ordinamento o un filtro, si sarebbe chiamato filtro o ordinamento , non func(proprio come lo hai chiamato tu IFilter, ecc.). In generale, in FP lo nomineresti funco fquando uno sorto filterè una scelta valida! Ad esempio, quando si scrive una funzione di ordine superiore che accetta funccome parametro.
Andres F.

10

Quando ho preso la mia classe Java anni fa, ci aspettavamo di mostrare le nostre soluzioni all'intera classe, quindi ho potuto vedere come la gente pensa; come risolvono i problemi logicamente. Mi aspettavo che le soluzioni si raggruppassero in tre o quattro soluzioni comuni. Invece, ho visto come 30 studenti hanno risolto il problema in 30 modi completamente diversi.

Naturalmente, man mano che i programmatori alle prime armi acquisiscono esperienza, acquisiranno esposizione a schemi software comuni, inizieranno a utilizzare tali schemi nel loro codice e quindi le loro soluzioni potrebbero fondersi su alcune strategie ottimali. Questi schemi formano un linguaggio tecnico attraverso il quale gli sviluppatori esperti possono comunicare.

Il linguaggio tecnico alla base della programmazione funzionale è la matematica . Di conseguenza, i problemi più adatti a risolvere utilizzando la programmazione funzionale sono essenzialmente problemi matematici. In matematica, non mi riferisco al tipo di matematica che vedresti nelle soluzioni aziendali, come addizione e sottrazione. Piuttosto, sto parlando del tipo di matematica che potresti vedere su Math Overflow o del tipo di matematica che vedresti nel motore di ricerca di Orbitz (è scritto in Lisp).

Questo orientamento ha alcune importanti conseguenze per i programmatori funzionali che cercano di risolvere i problemi di programmazione del mondo reale:

  1. La programmazione funzionale è più dichiarativa che imperativa; si occupa principalmente di dire al computer cosa fare, non come farlo.

  2. I programmi orientati agli oggetti sono spesso costruiti dall'alto verso il basso. Viene creato un progetto di classe e i dettagli vengono compilati. I programmi funzionali sono spesso costruiti dal basso verso l'alto, a partire da piccole funzioni dettagliate che vengono combinate in funzioni di livello superiore.

  3. La programmazione funzionale può avere vantaggi per la prototipazione di logiche complesse, la costruzione di programmi flessibili che possono cambiare ed evolversi organicamente e la creazione di software in cui la progettazione iniziale non è chiara.

  4. I programmi orientati agli oggetti possono essere più adatti ai domini aziendali poiché le classi, i messaggi tra gli oggetti e i modelli software forniscono tutti una struttura che si associa al dominio aziendale, acquisisce la sua business intelligence e lo documenta.

  5. Poiché tutto il software pratico produce effetti collaterali (I / O), i linguaggi di programmazione puramente funzionali richiedono un meccanismo per produrre tali effetti collaterali pur rimanendo matematicamente puri (monadi).

  6. I programmi funzionali possono essere dimostrati più facilmente, a causa della loro natura matematica. Il sistema di tipi di Haskell può trovare cose in fase di compilazione che la maggior parte delle lingue OO non riesce.

E così via. Come per molte cose nel campo dell'informatica, i linguaggi orientati agli oggetti e i linguaggi funzionali hanno diversi compromessi.

Alcuni moderni linguaggi OO hanno adottato alcuni degli utili concetti di programmazione funzionale, in modo da poter avere il meglio di entrambi i mondi. Linq e le funzionalità del linguaggio che sono state aggiunte per supportarlo, ne sono un buon esempio.


6
@thepacker: hai uno stato nella programmazione funzionale. Solo la rappresentazione è diversa: in OOP si utilizzano variabili mutabili mentre nella programmazione funzionale è possibile utilizzare flussi infiniti di stati che vengono creati al volo quando il programma deve ispezionarli. Sfortunatamente, lo stato viene spesso identificato con variabili mutabili, come se le variabili mutabili fossero l'unico modo per rappresentare lo stato. Di conseguenza, molti credono che un linguaggio di programmazione senza variabili mutabili non possa modellare lo stato.
Giorgio,

12
"Di conseguenza, i problemi più adatti a risolvere utilizzando la programmazione funzionale sono essenzialmente problemi matematici.": Non sono d'accordo con questa affermazione (vedi anche il mio commento precedente), almeno non vedo perché dovrebbe essere vero o cosa in realtà significa. Puoi vedere attraversare una struttura di directory come un problema matematico e implementarlo con una funzione ricorsiva, ma poiché il cliente non vede il codice sorgente, stai solo risolvendo un problema pratico come trovare un file da qualche parte nel file system. Trovo artificiale una tale distinzione tra problemi matematici e non matematici.
Giorgio,

5
+1, ma non sono venduto ai punti 2 e 4. Ho visto molti tutorial e post sul blog di Haskell che iniziano affrontando un problema dall'alto verso il basso, definendo tutti i tipi e le operazioni per vedere come tutto si adatta mentre lasciando ogni definizione di valore e funzione non definita (bene, definita per generare un'eccezione). Mi sembra che i programmatori Haskell tendano a modellare il problema in modo più approfondito perché sono più inclini ad aggiungere tipi banali per il bene della semantica - ad esempio aggiungendo un SafeStringtipo per rappresentare stringhe che sono state salvate in HTML - e generalmente tengono più traccia di effetti.
Doval,

4
"i problemi più adatti a risolvere utilizzando la programmazione funzionale sono essenzialmente problemi matematici". Questo semplicemente non è vero. Il fatto che il concetto di Monadi derivi anche dalla teoria delle categorie non significa che i compiti matematici siano il dominio più naturale / adatto ai linguaggi funzionali. Significa solo che il codice scritto in linguaggi funzionali fortemente tipizzati può essere descritto, analizzato e motivato sull'uso della matematica. Questa è una potente funzione extra, non qualcosa che ti impedisce di scrivere "Barbie Horse Adventures" in Haskell.
itsbruce,

6
@itsbruce Barbie Horse Adventures è una seria simulazione matematica di altissimo livello, è un pidgeon che fora FP in proiezioni numeriche troppo complesse come matrici che descrivono il luccichio fisico dei capelli di Barbie su un periodo discreto in una varietà di possibili ambienti del mondo reale che convincono le persone a respingerlo completamente come irrilevante per la loro codifica quotidiana dei problemi aziendali.
Jimmy Hoffa, il

7

Vorrei sottolineare un aspetto che ritengo importante e che non è stato trattato nelle altre risposte.

Prima di tutto, penso che il divario rappresentativo tra problemi e soluzioni possa essere più nella mente del programmatore, in base al loro background e ai concetti con cui hanno più familiarità.

OOP e FP esaminano i dati e le operazioni da due diverse prospettive e offrono diversi compromessi, come già sottolineato da Robert Harvey.

Un aspetto importante in cui differiscono è il modo in cui consentono di estendere il software.

Considera la situazione in cui hai una raccolta di tipi di dati e una raccolta di operazioni, ad esempio hai diversi formati di immagine e mantieni una libreria di algoritmi per elaborare le immagini.

La programmazione funzionale semplifica l'aggiunta di nuove operazioni al software: è necessaria solo una modifica locale nel codice, in particolare si aggiunge una nuova funzione, che gestisce diversi formati di dati di input. D'altro canto, l'aggiunta di nuovi formati è più complessa: è necessario modificare tutte le funzioni già implementate (modifica non locale).

L'approccio orientato agli oggetti è simmetrico a questo: ogni tipo di dati porta la propria implementazione di tutte le operazioni ed è responsabile della scelta della corretta implementazione in fase di esecuzione (dispacciamento dinamico). Ciò semplifica l'aggiunta di un nuovo tipo di dati (ad esempio un nuovo formato di immagine): basta aggiungere una nuova classe e implementare tutti i suoi metodi. D'altra parte, aggiungere una nuova operazione significa cambiare tutte le classi che devono fornire quell'operazione. In molte lingue ciò avviene estendendo un'interfaccia e adattando tutte le classi che la implementano.

Questo diverso approccio all'estensione è uno dei motivi per cui OOP è più appropriato per alcuni problemi in cui l'insieme di operazioni varia meno spesso dell'insieme di tipi di dati su cui queste operazioni funzionano. Un esempio tipico sono le GUI: si dispone di un insieme fisso di operazioni che tutti i widget devono implementare ( paint, resize, movee così via) e una raccolta di widget che si desidera estendere.

Quindi, secondo questa dimensione, OOP e FP sono solo due modi speculari per organizzare il tuo codice. Vedere SICP , in particolare la Sezione 2.4.3, la Tabella 2.22 e il passaggio del messaggio di paragrafo .

Riassumendo: in OOP si utilizzano i dati per organizzare le operazioni, in FP si utilizzano le operazioni per organizzare i dati. Ogni approccio è più forte o più debole in base al contesto. In generale, nessuno dei due ha un divario rappresentativo più elevato tra problema e soluzione.


1
In realtà, Visitor Pattern (in OOP) ti dà la stessa potenza di quella che hai detto per FP. Ti consente di aggiungere nuove operazioni mentre rende più difficile l'aggiunta di nuove classi. Mi chiedo se puoi fare lo stesso in FP (es. Aggiungendo nuovi formati invece di operazioni).
Euforico,

1
Lo scenario di elaborazione delle immagini non sembra il miglior esempio. Sicuramente convertiresti le immagini in un formato interno e lo trasformeresti invece di scrivere implementazioni separate di tutte le tue cose di elaborazione delle immagini per ogni tipo di immagine?
Ehi,

1
@Giorgio forse non sto ottenendo il significato previsto da questa parte: "l'aggiunta di nuovi formati è più complicata: devi cambiare tutte le funzioni che hai già implementato".
Ehi,

2
@Hey Giorgio si riferisce probabilmente al cosiddetto Problema di espressione . "Aggiungere nuovi formati" significa "aggiungere nuovi costruttori (noti anche come" casi ") a un tipo di dati".
Andres F.

1
@Euforico: non ho esaminato i dettagli, ma la controparte FP del modello visitatore dovrebbe comportare una Othervariante / costruttore nel tipo di dati e un parametro di funzione extra da passare a tutte le funzioni per gestire l' altro caso. È, ovviamente, imbarazzante / meno naturale rispetto alla soluzione OOP (spedizione dinamica). Allo stesso modo, il modello di visitatore è scomodo / meno naturale della soluzione FP (una funzione di ordine superiore).
Giorgio,

5

La maggior parte dei linguaggi funzionali non sono orientati agli oggetti. Ciò non significa che non abbiano oggetti (nel senso di tipi complessi a cui sono associate funzionalità specifiche). Haskell, come Java, ha liste, mappe, array, tutti i tipi di alberi e molti altri tipi complessi. Se guardi il modulo Haskell List o Map vedrai un insieme di funzioni molto simili ai metodi nella maggior parte dei moduli di libreria in linguaggio OO equivalenti. Se ispezioni il codice, troverai persino incapsulamenti simili, con alcuni tipi (o i loro costruttori, per la precisione) e funzioni utilizzabili solo da altre funzioni nel modulo.

Il modo in cui lavori con questi tipi in Haskell è spesso poco diverso dal modo OO. In Haskell dico

  null xs
  length xs

e in Java dici

  xs.isEmpty
  xs.length

Pomodoro, Pomodoro. Le funzioni di Haskell non sono legate all'oggetto ma sono strettamente associate al suo tipo. "Ah, ma ," dici, "in Java sta chiamando quale metodo di ogni lunghezza sia appropriato alla classe effettiva di quell'oggetto". Sorpresa - Haskell fa anche il polimorfismo con le classi di tipi (e altre cose).

In Haskell, se ho è - una raccolta di numeri interi - non importa se la collezione è una lista o un set o un array, questo codice

  fmap (* 2) is

restituirà una raccolta con tutti gli elementi raddoppiati. Polimorfismo significa che verrà chiamata la funzione di mappa appropriata per il tipo specifico.

Questi esempi sono, in verità, tipi non molto complessi ma lo stesso vale per problemi più difficili. L'idea che i linguaggi funzionali non ti permettano di modellare oggetti complessi e di associare loro funzionalità specifiche è semplicemente sbagliata.

Ci sono differenze importanti e significative tra lo stile funzionale e quello OO, ma non credo che questa risposta debba occuparsene. Hai chiesto se i linguaggi funzionali impediscono (o ostacolano) la modellazione intuitiva di problemi e attività. La risposta è no.


È un dato di fatto, è possibile utilizzare le classi di tipo in Java / C #; devi solo passare esplicitamente il dizionario delle funzioni, invece di far dedurre dal compilatore quello corretto in base al tipo.
Doval,

Oh, sicuramente. Come ha detto William Cook, molto codice OO in realtà fa un uso più frequente di funzioni di ordine superiore rispetto al codice funzionale, anche se probabilmente il programmatore non ne è a conoscenza.
itsbruce,

2
Stai facendo una falsa distinzione. Un elenco non è più una struttura di dati inerte di uno stack o una coda o un simulatore di tic-tac-toe. Un elenco può contenere qualsiasi cosa (in Haskell potrebbe essere parzialmente applicato funzioni) e definisce come interagire con queste cose. È possibile applicare un elenco pieno di funzioni parzialmente applicate a un altro elenco di valori e il risultato sarebbe un nuovo elenco contenente ogni possibile combinazione di funzioni dal primo e input dal secondo. Questo è molto più di una struttura a C.
itsbruce,

3
I tipi sono usati per cose più varie nei linguaggi funzionali rispetto a quelli imperativi / OO (i sistemi dei tipi tendono ad essere più potenti e coerenti, per prima cosa). I tipi e le loro funzioni vengono utilizzati per modellare i percorsi del codice (motivo per cui diversi linguaggi funzionali semplicemente non hanno parole chiave per i loop - non necessari), ad esempio.
itsbruce,

4
@Fuhrmanator "Non vedo come si fa in FP" Quindi ti lamenti di qualcosa che non capisci non ha senso; vai a impararlo, capiscilo a fondo e poi avrà senso. Forse dopo avrà senso deciderai che è una schifezza e non hai bisogno che altri te lo dimostrino, anche se altre persone come quelle sopra lo capiscono e non sono giunti a quella conclusione. Sembra che tu abbia portato un coltello in uno scontro a fuoco, e ora stai dicendo a tutti che le pistole sono inutili perché non sono affilate.
Jimmy Hoffa,

4

FP si impegna davvero a ridurre il divario rappresentativo:

Qualcosa che vedrai molto nei linguaggi funzionali è la pratica di costruire il linguaggio su (usando la progettazione bottom-up) in un linguaggio specifico del dominio incorporato (EDSL) . Ciò ti consente di sviluppare un mezzo per esprimere le tue preoccupazioni aziendali in modo naturale per il tuo dominio all'interno del linguaggio di programmazione. Haskell e Lisp sono entrambi orgogliosi di questo.

Parte (se non tutto) di ciò che abilita questa capacità è maggiore flessibilità ed espressività all'interno del linguaggio o dei linguaggi di base stessi; con funzioni di prima classe, funzioni di ordine superiore, composizione delle funzioni e, in alcune lingue, tipi di dati algebrici (sindacati discriminati AKA) e capacità di definire operatori *, c'è più flessibilità disponibile su come esprimere le cose di quanto non ci sia con OOP, che significa che probabilmente troverai modi più naturali per esprimere le cose dal tuo dominio problematico. (Dovrebbe dire qualcosa che molte lingue OOP hanno recentemente adottato molte di queste funzionalità, se non iniziando con esse.)

* Sì, in molte lingue OOP è possibile sovrascrivere gli operatori standard, ma in Haskell è possibile definirne di nuovi!

Nella mia esperienza di lavoro con linguaggi funzionali (principalmente F # e Haskell, alcuni Clojure e un po 'di Lisp ed Erlang), ho più tempo a mappare lo spazio del problema nella lingua rispetto a OOP, specialmente con tipi di dati algebrici, che trovo più flessibile delle lezioni. Qualcosa che mi ha lasciato senza fiato quando ho iniziato con FP, in particolare con Haskell, era che dovevo pensare / lavorare a un livello leggermente più alto di quello a cui ero abituato in linguaggi imperativi o OOP; potresti anche imbatterti in questo.


Le preoccupazioni aziendali (da parte del cliente) non vengono espresse molto spesso nelle funzioni di ordine superiore. Ma un cliente può scrivere una user story di una riga e fornirti le regole aziendali (oppure puoi farle uscire dal cliente). Sono interessato alla modellazione del dominio (dall'alto verso il basso?) (Da quei manufatti) che ci consente di arrivare alle astrazioni del problema che si associano a funzioni di prima classe, ecc. Puoi citare alcuni riferimenti? Puoi usare un esempio concreto di un dominio, delle astrazioni, ecc.?
Fuhrmanator,

3
@Fuhrmanator Un cliente può ancora scrivere user story di una riga e regole di business indipendentemente dal fatto che tu usi OOP e FP . Non mi aspetto che i clienti comprendano le funzioni di ordine superiore non più di quanto mi aspetto che capiscano eredità, composizione o interfacce.
Andres F.

1
@Fuhrmanator Con che frequenza il cliente ha espresso le proprie preoccupazioni in termini di generici Java o classi e interfacce di base astratte? Mio Dio. Paul ti ha dato un'ottima risposta, spiegando un metodo pratico e affidabile di modellizzazione usando tecniche che sicuramente non sono troppo difficili da visualizzare. Eppure insisti sul fatto che sia descritto in termini di una specifica tecnica di modellazione per un particolare paradigma, come se quello fosse in qualche modo il modo più naturale di rappresentare il design e qualsiasi altra cosa può essere ridisegnata nei suoi termini.
itsbruce,

@Fuhrmanator Questo potrebbe non essere di alto livello come quello che stai cercando, ma dovrebbe dare un'idea del processo di progettazione usando tecniche funzionali: progettare con i tipi . Consiglio di navigare in quel sito in generale perché ha alcuni articoli eccellenti.
Paolo

@Fuhrmanator C'è anche questa serie Funzionalmente pensante
paul

3

Come diceva Niklaus Wirth, "Algorithms + Data Structures = Programs". La programmazione funzionale riguarda il modo di organizzare gli algoritmi e non dice molto sui modi di organizzare le strutture di dati. In effetti, esistono linguaggi FP sia con variabili mutabili (Lisp) che immutabili (Haskell, Erlang). Se vuoi confrontare e confrontare FP con qualcosa, dovresti scegliere la programmazione imperativa (C, Java) e dichiarativa (Prolog).

OOP è d'altra parte un modo per costruire strutture di dati e collegarvi algoritmi. FP non ti impedisce di costruire strutture di dati simili a quelle OOP. Se non mi credi, dai un'occhiata alle dichiarazioni sul tipo di Haskell. Tuttavia, la maggior parte dei linguaggi FP concordano sul fatto che le funzioni non appartengono alle strutture dati e dovrebbero piuttosto trovarsi nello stesso spazio dei nomi. Questo viene fatto al fine di semplificare la costruzione di nuovi algoritmi tramite la composizione delle funzioni. In effetti, se sai quale input riceve una funzione e quale output produce, perché dovrebbe importare anche a quale struttura di dati appartiene? Ad esempio, perché la addfunzione deve essere chiamata su un'istanza di tipo Integer e ha passato un argomento invece di passare semplicemente due argomenti Integer?

Pertanto, non vedo perché FP dovrebbe rendere le soluzioni più difficili da comprendere in generale, e non penso che lo faccia comunque. Tuttavia, tieni presente che un programmatore imperativo esperto troverà sicuramente programmi funzionali più difficili da comprendere rispetto a quelli imperativi, e viceversa. Questo è simile alla dicotomia Windows - Linux quando le persone che hanno investito 10 anni per familiarizzare con l'ambiente Windows trovano Linux difficile dopo un mese di utilizzo e coloro che sono abituati a Linux non possono raggiungere la stessa produttività in Windows. Quindi, il "divario rappresentazionale" di cui stai parlando è molto soggettivo.


A causa dell'incapsulamento e del nascondimento dei dati, che non sono principi OO di per sé, non sono del tutto d'accordo con OO essendo strutture di dati + algoritmi (questa è solo la vista interna). Il bello di ogni buona astrazione è che c'è un servizio che fornisce per risolvere parte del problema, senza che uno abbia bisogno di capire troppi dettagli. Penso che stai parlando di incapsulamento quando separi funzioni e dati in FP, ma sono troppo verde per saperlo con certezza. Inoltre, ho modificato la mia domanda per fare riferimento alla traccia dalla soluzione al problema (per chiarire il significato del divario rappresentativo).
Fuhrmanator,

if you know what input a function takes and what output it produces, why should it matter which data structure it belongs too?Sembra molto simile alla coesione, che per qualsiasi sistema modulare è importante. Direi che non sapere dove trovare una funzione renderebbe più difficile la comprensione di una soluzione, dovuta a un maggiore divario rappresentativo. Molti sistemi OO hanno questo problema, ma è a causa della cattiva progettazione (bassa coesione). Ancora una volta, il problema dello spazio dei nomi non rientra nella mia esperienza in FP, quindi forse non è così grave come sembra.
Fuhrmanator,

1
@Fuhrmanator Ma tu non sai dove trovare una funzione. E c'è solo una funzione, non più funzioni con lo stesso nome, definite per ciascun tipo di dati (di nuovo, vedi "Problema di espressione"). Ad esempio, per le funzioni della libreria Haskell (che sei incoraggiato a usare, invece di riscoprire la ruota o creare versioni leggermente meno utili di tali funzioni), hai meravigliosi strumenti di scoperta come Hoogle , che è così potente da permetterti di cercare per firma (provalo! :))
Andres F.

1
@Fuhrmanator, dopo "Sembra molto simile alla coesione", mi sarei aspettato che tu concludessi logicamente che i linguaggi FP ti permettessero di scrivere programmi con una coesione più elevata ma mi hai sorpreso. :-) Penso che dovresti scegliere un linguaggio FP, impararlo e decidere da solo. Se fossi in te, suggerirei di iniziare con Haskell dal momento che è abbastanza puro e quindi non ti lascerò scrivere codice in stile imperativo. Anche Erlang e alcuni Lisp sono buone scelte ma si mescolano in altri stili di programmazione. Scala e Clojure, IMO, sono piuttosto complicati da cui partire.
MKalkov,
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.