Esiste una metodologia di ingegneria del software per la programmazione funzionale? [chiuso]


203

L'ingegneria del software come viene insegnata oggi è interamente focalizzata sulla programmazione orientata agli oggetti e sulla visione del mondo "naturale" orientata agli oggetti. Esiste una metodologia dettagliata che descrive come trasformare un modello di dominio in un modello di classe con diversi passaggi e molti artefatti (UML) come diagrammi di casi d'uso o diagrammi di classe. Molti programmatori hanno interiorizzato questo approccio e hanno una buona idea su come progettare da zero un'applicazione orientata agli oggetti.

Il nuovo hype è la programmazione funzionale, che viene insegnata in molti libri e tutorial. Ma che dire dell'ingegneria del software funzionale? Durante la lettura di Lisp e Clojure, sono venuto su due dichiarazioni interessanti:

  1. I programmi funzionali sono spesso sviluppati dal basso verso l'alto invece che dall'alto verso il basso ("On Lisp", Paul Graham)

  2. I programmatori funzionali usano le mappe in cui i programmatori OO usano oggetti / classi ("Clojure per programmatori Java", interazione di Rich Hickley).

Quindi qual è la metodologia per una progettazione sistematica (basata sul modello?) Di un'applicazione funzionale, cioè in Lisp o Clojure? Quali sono i passaggi comuni, quali artefatti uso, come posso mapparli dallo spazio problematico allo spazio della soluzione?


3
Ho un commento qui: molti programmi sono scritti dall'alto verso il basso, un'esposizione pratica al processo di sviluppo del software in un linguaggio funzionale è data nel libro "Programmazione funzionale in Concurrent Clean" (il linguaggio stesso è molto accademico, anche se).
Artyom Shalkhakov,

4
1. Parnas sostiene che la maggior parte dei programmi dovrebbe essere bottom-up e quindi falsificata per apparire come top-down, quindi quegli approcci dovrebbero essere mescolati, non esiste una risposta giusta.
Gabriel Ščerbák,

2
2. Gli oggetti forniscono comportamento in base al loro stato strutturato incapsulato, in FP hai esplicito tutto lo stato e la struttura e il comportamento (funzioni) è separato dalla struttura. Pertanto, per la modellazione dei dati, si utilizzano le mappe per gli oggetti, ma quando si progettano applicazioni, gli oggetti non possono essere sostituiti con funzioni: FP è una grande espressione generata e valutata attraverso pipeline, OOP riguarda la creazione del modello e l'invio di messaggi tra oggetti.
Gabriel Ščerbák,

1
Qualche tempo fa ho posto una domanda correlata: "come si modellano i dati dai database relazionali in clojure?" stackoverflow.com/questions/3067261/…
Sandeep

4
Hehe, a proposito delle lezioni del SICP, dice Hal Abelson, mezzo per scherzo, qualcosa del tipo "Esiste una metodologia famosa, o dovrei dire mitologia, chiamata ingegneria del software [...] facendo diagrammi e requisiti complicati e poi costruendo sistemi con loro, quelle persone non hanno programmato molto ". Vengo da una "Java School", dove per secoli abbiamo insegnato UML e artefatti e cose del genere, e mentre un po 'è buono, troppa pianificazione e schemi (gioco di parole) sono più dannosi che utili: non sai mai come il software sarà fino a quando non si arriva effettivamente al codice.
lfborjas,

Risposte:


165

Grazie a Dio, gli ingegneri del software non hanno ancora scoperto la programmazione funzionale. Ecco alcuni parallelismi:

  • Molti "modelli di progettazione" OO vengono acquisiti come funzioni di ordine superiore. Ad esempio, il modello Visitatore è noto nel mondo funzionale come una "piega" (o se sei un teorico dalla testa appuntita, un "catamorfismo"). Nei linguaggi funzionali, i tipi di dati sono principalmente alberi o tuple e ad ogni tipo di albero è associato un catamorfismo naturale.

    Queste funzioni di ordine superiore sono spesso accompagnate da alcune leggi di programmazione, dette anche "teoremi liberi".

  • I programmatori funzionali usano gli schemi molto meno pesantemente dei programmatori OO. Gran parte di ciò che viene espresso nei diagrammi OO viene invece espresso in tipi o in "firme", che dovresti considerare come "tipi di modulo". Haskell ha anche "classi di tipo", che è un po 'come un tipo di interfaccia.

    Quei programmatori funzionali che usano i tipi generalmente pensano che "una volta che si ottengono i tipi giusti, il codice praticamente si scrive da solo".

    Non tutti i linguaggi funzionali utilizzano tipi espliciti, ma il libro How To Design Programs , un libro eccellente per l'apprendimento di Scheme / Lisp / Clojure, si basa fortemente su "descrizioni dei dati", che sono strettamente correlate ai tipi.

Quindi qual è la metodologia per una progettazione sistematica (basata sul modello?) Di un'applicazione funzionale, cioè in Lisp o Clojure?

Qualsiasi metodo di progettazione basato sull'astrazione dei dati funziona bene. Mi capita di pensare che questo sia più facile quando la lingua ha tipi espliciti, ma funziona anche senza. Un buon libro sui metodi di progettazione per tipi di dati astratti, che si adatta facilmente alla programmazione funzionale, è Abstraction and Specification in Program Development di Barbara Liskov e John Guttag, la prima edizione. Liskov ha vinto il premio Turing in parte per quel lavoro.

Un'altra metodologia di progettazione unica in Lisp è quella di decidere quali estensioni di lingua sarebbero utili nel dominio del problema in cui si sta lavorando, quindi utilizzare macro igieniche per aggiungere questi costrutti alla propria lingua. Un buon posto in cui leggere questo tipo di design è l'articolo di Matthew Flatt che sta creando le lingue nella racchetta . L'articolo potrebbe essere dietro un paywall. Puoi anche trovare materiale più generale su questo tipo di design cercando il termine "linguaggio incorporato specifico del dominio"; per consigli ed esempi particolari oltre a quelli di Matthew Flatt, probabilmente inizierei con Graham's On Lisp o forse ANSI Common Lisp .

Quali sono i passaggi comuni, quali artefatti utilizzo?

Passaggi comuni:

  1. Identificare i dati nel programma e le operazioni su di esso e definire un tipo di dati astratto che rappresenti questi dati.

  2. Identificare azioni o schemi di calcolo comuni ed esprimerli come funzioni o macro di ordine superiore. Aspettati di fare questo passo come parte del refactoring.

  3. Se stai usando un linguaggio funzionale digitato, usa il controllo tipo presto e spesso. Se stai usando Lisp o Clojure, la migliore pratica è scrivere prima i contratti di funzione includendo i test unitari: è lo sviluppo guidato dai test al massimo. E vorrai utilizzare qualsiasi versione di QuickCheck è stata trasferita sulla tua piattaforma, che nel tuo caso sembra che si chiama ClojureCheck . È una libreria estremamente potente per la costruzione di test casuali di codice che utilizza funzioni di ordine superiore.


2
Il visitatore IMO non è fold - fold è un sottoinsieme di visitatori. La spedizione multipla non viene (direttamente) catturata da fold.
Michael Ekstrand,

6
@Michael - in realtà puoi catturare più spedizioni con vari tipi di catamorfismi di ordine superiore in modo molto ordinato. Il lavoro di Jeremy Gibbons è un posto dove cercarlo, ma raccomanderei di lavorare sulla programmazione generica di tipi di dati in generale - Sono particolarmente affezionato al documento composito.
sclv,

6
Sono d'accordo sul fatto che vedo diagrammi usati molto meno frequentemente per descrivere progetti funzionali e penso che sia un peccato. È certamente difficile rappresentare l'equivalente di un diagramma di sequenza quando si usa molto HOF. Ma vorrei che lo spazio su come descrivere i progetti funzionali con le immagini fosse esplorato meglio. Per quanto odio UML (come spec), trovo che UML (come sketch) sia abbastanza utile in Java e vorrei che esistessero le migliori pratiche su come fare l'equivalente. Ho provato un po 'a fare questo con protocolli e record Clojure, ma non ho nulla che mi piaccia davvero.
Alex Miller,

22
+1 per "Grazie a Dio che gli ingegneri del software non hanno ancora scoperto la programmazione funzionale". ;)
Aky,

1
OO è esso stesso un modo di provare a programmare con i tipi, quindi gli approcci non sono così diversi. Il problema con i progetti OO di solito sembra derivare da persone che non sanno cosa stanno facendo.
Marcin,

46

Per Clojure, consiglio di tornare a una buona vecchia modellazione relazionale. Out of the Tarpit è una lettura ispiratrice.


Questo è un ottimo articolo, i bei vecchi tempi in Informatica devono essere stati davvero straordinariamente buoni, quando tutti questi concetti sono sopravvissuti fino al rinascimento di oggi. Probabilmente è dovuto alle solide basi matematiche.
Thorsten,

1
Questo. QUESTO. QUESTO! Sto leggendo questo documento ed è davvero interessante come sembra coprire tutte le basi di ciò che serve per costruire sistemi reali, mantenendo allo stesso tempo uno stato mutabile minimo in modo altamente controllato. Sto giocando con la costruzione di Pong e Tetris in uno stile FRelP (scusate lo strano inizialismo, ma c'è già un altro FRP più popolare: Programmazione reattiva funzionale).
John Cromartie,

Dopo aver letto l'articolo, penso che il clojure sarebbe il linguaggio perfetto per FR (el) P, almeno per la logica essenziale , lo stato accidentale e il controllo e gli altri componenti. Mi chiedo come fare una definizione relazionale dello stato essenziale nel clojure senza reinventare sql (senza i suoi difetti)? Oppure l'idea è semplicemente quella di utilizzare un buon DB relazionale (sql) e costruire un programma funzionale al di sopra di esso senza la discrepanza concettuale introdotta da OOP?
Thorsten,

1
@Thorsten l'idea di base è impostata = tabella, mappa = indice. La parte difficile è mantenere sincronizzati indici e tabelle, ma questo problema può essere risolto con tipi di set migliori. Un tipo di set semplice che ho implementato è il set con chiave, che è un set che utilizza una funzione chiave per verificare l'unicità. Ciò significa che, combinando un valore inserire o aggiornare, chiamare get con i campi chiave primaria restituisce l'intera riga.
cgrand,


38

Personalmente trovo che tutte le solite buone pratiche dello sviluppo di OO si applichino anche alla programmazione funzionale - solo con alcune piccole modifiche per tenere conto della visione del mondo funzionale. Dal punto di vista metodologico, non è necessario fare nulla di fondamentalmente diverso.

La mia esperienza deriva dal passaggio da Java a Clojure negli ultimi anni.

Qualche esempio:

  • Comprendi il tuo dominio aziendale / modello di dati - altrettanto importante se stai progettando un modello a oggetti o crei una struttura di dati funzionale con mappe nidificate. In un certo senso, FP può essere più semplice perché ti incoraggia a pensare al modello di dati separatamente da funzioni / processi ma devi comunque fare entrambe le cose.

  • Orientamento al servizio nella progettazione : in realtà funziona molto bene dal punto di vista del FP, poiché un servizio tipico è in realtà solo una funzione con alcuni effetti collaterali. Penso che la visione "bottom-up" dello sviluppo del software a volte sposata nel mondo Lisp sia in realtà solo un buon principio di progettazione API orientato ai servizi in un'altra forma.

  • Test Driven Development - funziona bene con i linguaggi FP, a volte persino meglio perché le funzioni pure si prestano molto bene alla scrittura di test chiari e ripetibili senza la necessità di creare un ambiente di stato. Potresti anche voler costruire test separati per verificare l'integrità dei dati (ad esempio, questa mappa contiene tutte le chiavi che mi aspetto, per bilanciare il fatto che in una lingua OO la definizione della classe lo imporrebbe al momento della compilazione).

  • Prototying / iteration - funziona altrettanto bene con FP. Potresti anche essere in grado di prototipare dal vivo con gli utenti se diventi estremamente bravo a costruire strumenti / DSL e utilizzarli nel REPL.


3
Queste pratiche mi sembrano abbastanza familiari. Penso ancora che qualcuno dovrebbe scrivere l'equivalente funzionale di "Ingegneria del software orientata agli oggetti usando UML, Patterns e Java" di Bruegge / Dutoit invece del sesto libro "Programing in Clojure". Potrebbe essere chiamato "Ingegneria del software funzionale usando Clojure e ?? cosa ??". Usano UML e pattern in FP? Ricordo che Paul Graham scrisse che i modelli sono un segno di una mancanza di astrazione in Lisp, che dovrebbe essere risolta dall'introduzione di nuove macro.
Thorsten,

3
Ma se traduci schemi come buone pratiche, potrebbero esserci anche schemi nel mondo FP, che vale la pena condividere con i non inizializzati.
Thorsten,

2
Nel libro PAIP ci sono alcuni progetti di principi interessanti. norvig.com/paip.html
mathk

1
ci sono anche schemi di programmazione funzionale (schemi di ricorsione, ecc.)
Gabriel Ščerbák,

13

La programmazione OO accoppia strettamente i dati con il comportamento. La programmazione funzionale separa i due. Quindi non hai diagrammi di classe, ma hai strutture di dati e in particolare hai tipi di dati algebrici. Questi tipi possono essere scritti in modo da adattarsi perfettamente al tuo dominio, inclusa l'eliminazione di valori impossibili per costruzione.

Quindi non ci sono libri e libri su di esso, ma esiste un approccio ben definito, come dice il proverbio, che rende non rappresentabili valori impossibili.

In tal modo, è possibile effettuare una serie di scelte sulla rappresentazione di determinati tipi di dati come funzioni e, al contrario, rappresentare determinate funzioni come unione di tipi di dati, in modo da poter ottenere, ad esempio, serializzazione, specifiche più rigorose, ottimizzazione, ecc. .

Quindi, dato ciò, scrivi funzioni sui tuoi annunci in modo tale da stabilire una sorta di algebra - cioè ci sono leggi fisse che valgono per queste funzioni. Alcuni sono forse idempotenti, lo stesso dopo più applicazioni. Alcuni sono associativi. Alcuni sono transitivi, ecc.

Ora hai un dominio su cui hai funzioni che si compongono secondo leggi ben educate. Un semplice DSL incorporato!

Oh, e date le proprietà, puoi ovviamente scrivere dei test automatizzati randomizzati (ala QuickCheck) .. e questo è solo l'inizio.


1
L'approccio di rendere non rappresentabili valori impossibili è meno applicabile alle lingue con tipizzazione dinamica come Clojure e Scheme che alle lingue con tipizzazione statica come Haskell e ML.
Zak,

@Zak - beh, non puoi verificare staticamente che non siano rappresentabili, ma puoi comunque costruire le tue strutture di dati allo stesso modo.
sclv,

7

Il design orientato agli oggetti non è la stessa cosa dell'ingegneria del software. L'ingegneria del software ha a che fare con l'intero processo di passaggio dai requisiti a un sistema funzionante, in tempo e con un basso tasso di difetti. La programmazione funzionale può essere diversa da OO, ma non elimina i requisiti, i progetti dettagliati e di alto livello, la verifica e il collaudo, le metriche del software, la stima e tutte le altre "cose ​​di ingegneria del software".

Inoltre, i programmi funzionali presentano modularità e altre strutture. I tuoi progetti dettagliati devono essere espressi in termini di concetti in quella struttura.


5

Un approccio consiste nel creare un DSL interno nel linguaggio di programmazione funzionale prescelto. Il "modello" è quindi un insieme di regole aziendali espresse nel DSL.


1
Comprendo l'approccio per costruire prima il linguaggio verso il dominio problematico fino a quando non viene raggiunto un livello di astrazione che non si verifica più schemi ripetitivi nel codice, piuttosto che risolvere il problema con tali astrazioni.
Thorsten,

1
Ma come appare quando "il modello è un insieme di regole aziendali espresse nel DSL"? In un'applicazione Java EE il modello è scritto come entità POJO, chiamate da controller-EJB che a loro volta aggiornano view-JSP - ad esempio. Ci sono modelli architettonici simili (come il modello MVC) in FP? Che aspetto ha?
Thorsten,

2
Non c'è motivo per cui non si possa avere un pattern MVC in FP, proprio così. FP ti consente ancora di creare strutture dati avanzate e, probabilmente, con ADT e abbinamento di modelli, ti consente di crearne di molto più ricche . Semmai, poiché FP separa dati e comportamento, i sistemi di tipo MVC nascono in modo molto più naturale.
sclv,

5

Vedi la mia risposta a un altro post:

In che modo Clojure si avvicina alla separazione delle preoccupazioni?

Concordo sul fatto che è necessario scrivere di più sull'argomento su come strutturare le applicazioni di grandi dimensioni che utilizzano un approccio FP (in più occorre fare di più per documentare le interfacce utente basate su FP)


3
Mi piace il 90% di pipeline e il 10% di approccio macro. Sembra del tutto naturale pensare a un programma funzionale come una pipeline di trasformazioni su dati immutabili. Non sono sicuro se capisco cosa intendi con "inserisci tutta l'intelligenza nei dati, non nel codice", poiché l'approccio per avere 100 funzioni che lavorano su 1 struttura di dati (anziché 10 funzioni su 10 strutture di dati) sembra implicare l'opposto. Le strutture dati in OOP non sono più intelligenti che in FP, dal momento che hanno il loro comportamento integrato?
Thorsten,

3

Sebbene ciò possa essere considerato ingenuo e semplicistico, penso che "ricette di design" (un approccio sistematico alla risoluzione dei problemi applicato alla programmazione come sostenuto da Felleisen et al. Nel loro libro HtDP ) sarebbero vicine a ciò che sembra stia cercando.

Ecco alcuni link:

http://www.northeastern.edu/magazine/0301/programming.html

http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.86.8371


Il link alla pagina nord-orientale sembra essere morto.
James Kingsbery,

1
James, hai ragione, e non ricordo cosa ci fosse dentro per sistemarlo, sfortunatamente. So solo che gli autori di HtDP hanno continuato a creare il linguaggio Pyret (e probabilmente stanno rivedendo la 2a edizione di HtDP per usarlo al posto di Racket, precedentemente PLT Scheme).
Artyom Shalkhakov,

3

Recentemente ho trovato questo libro: Modellazione di domini funzionale e reattiva

Penso che sia perfettamente in linea con la tua domanda.

Dalla descrizione del libro:

La modellazione di domini funzionale e reattiva ti insegna come pensare al modello di dominio in termini di funzioni pure e come comporle per costruire astrazioni più grandi. Inizierai con le basi della programmazione funzionale e progredirai gradualmente verso i concetti e i modelli avanzati che devi conoscere per implementare modelli di dominio complessi. Il libro dimostra come modelli FP avanzati come tipi di dati algebrici, design basato sulla classe di caratteri e isolamento degli effetti collaterali possono far comporre il tuo modello per leggibilità e verificabilità.


2

Esiste lo stile "calcolo del programma" / "progettazione mediante calcolo" associato al prof. Richard Bird e al gruppo Algebra of Programming dell'Università di Oxford (Regno Unito), non credo sia troppo inverosimile per considerare questa metodologia.

Personalmente, mentre mi piace il lavoro prodotto dal gruppo AoP, non ho la disciplina di praticare il design in questo modo. Tuttavia questo è il mio difetto, e non quello del calcolo del programma.


2

Ho trovato che lo sviluppo guidato dal comportamento è una misura naturale per lo sviluppo rapido di codice sia in Clojure che in SBCL. Il vero vantaggio di sfruttare BDD con un linguaggio funzionale è che tendo a scrivere test unitari granulometrici molto più fini di quelli che faccio di solito quando uso i linguaggi procedurali perché faccio un lavoro molto migliore nel decomporre il problema in blocchi di funzionalità più piccoli.


quali sono gli strumenti che stai usando per fare BDD in clojure?
murtaza52,

Mi piace Midje. È aggiornato e molto espressivo. Dai
Marc

1

Onestamente, se vuoi ricette di design per programmi funzionali, dai un'occhiata alle librerie di funzioni standard come Preludio di Haskell. In FP, i modelli sono generalmente catturati da procedure di ordine superiore (funzioni che operano su funzioni) stesse. Quindi, se si vede un modello, spesso viene semplicemente creata una funzione di ordine superiore per catturare quel modello.

Un buon esempio è fmap. Questa funzione accetta una funzione come argomento e la applica a tutti gli "elementi" del secondo argomento. Poiché fa parte della classe di tipo Functor, qualsiasi istanza di un Functor (come un elenco, un grafico, ecc.) Può essere passata come secondo argomento a questa funzione. Cattura il comportamento generale dell'applicazione di una funzione a ogni elemento del suo secondo argomento.


-7

Bene,

Generalmente molti linguaggi di programmazione funzionale vengono utilizzati a lungo nelle università per "piccoli problemi con i giocattoli".

Ora stanno diventando più popolari poiché OOP ha difficoltà con la "programmazione parallela" a causa di "stato". E a volte lo stile funzionale è migliore per problemi a portata di mano come Google MapReduce.

Sono sicuro che, quando i ragazzi funzionali raggiungeranno il muro [provano a implementare sistemi più grandi di 1.000.000 di righe di codice], alcuni di loro arriveranno con nuove metodologie di ingegneria del software con parole d'ordine :-). Dovrebbero rispondere alla vecchia domanda: come dividere il sistema in pezzi in modo da poter "mordere" ogni pezzo uno alla volta? [lavorare in modo iterativo, incerementale ed evolutivo] usando lo stile funzionale.

È sicuro che lo stile funzionale avrà effetto sul nostro stile orientato agli oggetti. "Continuiamo" a molti concetti dei sistemi funzionali e adattati ai nostri linguaggi OOP.

Ma i programmi funzionali verranno utilizzati per sistemi così grandi? Diventeranno il flusso principale? Questa è la domanda .

E nessuno può venire con una metodologia realistica senza implementare sistemi così grandi, sporcandosi le mani. Prima dovresti sporcarti le mani, quindi suggerire una soluzione. Soluzioni-Suggerimenti senza "dolori reali e sporcizia" saranno "fantasia".


Ora ci sono stati abbastanza sistemi su larga scala costruiti con linguaggi funzionali. Anche se non ci fosse, questo non è affatto un argomento.
Svante,

Bene, nominarne alcuni? Conosco solo pochissimi sistemi "Erlang". [taglia media] Ma Haskel? Clojure? Lisp?
Hippias Minor,

E che [scrivere grandi sistemi] è il vero argomento. Perché questo è il caso di test. Questo caso di prova mostra che se questo stile funzionale è utile e possiamo fare cose pratiche con esso nel mondo reale.
Ippia minore

2
La cosa divertente delle lingue non analmente "OOP" è che spesso ti liberano dalle "metodologie di progettazione", di pensare da solo e di tagliare il tuo programma nel modo più appropriato, invece di seguire ciecamente uno schema prestabilito e vivere con caldaia burocratica. Siamo spiacenti, nessun corso di 3 settimane di 10 punti qui.
Svante,

1
Ho visto cose in cui non credevi.
Svante,
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.