La programmazione funzionale ignora i benefici ottenuti dal "sui criteri da utilizzare per la decomposizione di sistemi in moduli" (nascondimento dei dati)?

26

C'è un articolo classico chiamato sui criteri da utilizzare per decomporre i sistemi in moduli che ho appena letto per la prima volta Ha perfettamente senso per me ed è probabilmente uno di quegli articoli su cui è basato l'OOP. La sua conclusione:

We have tried to demonstrate by these examples that it is almost always incorrect to begin the decomposition of a system into modules on the basis of a flowchart. ... Each module is then designed to hide such a decision from the others

Nella mia opinione ignorante e inesperta, la programmazione funzionale prende esattamente il contrario di questo articolo. La mia comprensione è la programmazione funzionale che rende il flusso dei dati idiomatico. I dati passano da una funzione all'altra, ogni funzione è intimamente consapevole dei dati e "cambiandoli" lungo la strada . E penso di aver visto un discorso di Rich Hickey in cui parla di come il nascondimento dei dati sia sopravvalutato o inutile o qualcosa del genere, ma non riesco a ricordare con certezza.

  1. Per prima cosa voglio sapere se la mia valutazione è corretta. Il paradigma del FP e questo articolo filosoficamente non sono d'accordo?
  2. Supponendo che non siano d'accordo, in che modo FP "compensa" la mancanza di dati nascosti? Forse sacrificano l'occultamento dei dati, ma ottengono X, Y e Z. Mi piacerebbe sapere il motivo per cui X, Y e Z sono considerati più utili di quelli che nascondono i dati.
  3. Oppure, supponendo che non siano d'accordo, forse FP ritiene che nascondere i dati sia negativo. In tal caso, perché pensa che nascondere i dati sia sbagliato?
  4. Supponendo che siano d'accordo, mi piacerebbe sapere qual è l'implementazione di FP per nascondere i dati. È ovvio vederlo in OOP. Puoi avere un campo private a cui nessuno al di fuori della classe può accedere. Non c'è un'analogia evidente con questo in FP.
  5. Sento che ci sono altre domande che dovrei chiedere ma non so che dovrei chiedere. Sentiti libero di rispondere anche a quelli.

Aggiornamento

Ho trovato questo discorso di Neal Ford che ha uno slide molto rilevante esso. Inserirò lo screenshot qui:

    
posta Daniel Kaplan 05.10.2013 - 00:23
fonte

7 risposte

23

L'articolo che citi riguarda la modularità in generale e si applicherebbe ugualmente a programmi strutturati, funzionali e orientati agli oggetti. Ho già sentito parlare di questo articolo da qualcuno che era un grande OOP, ma l'ho letto come un articolo sulla programmazione in generale, non su uno specifico OOP. C'è un famoso articolo sulla programmazione funzionale, Perché la programmazione funzionale è importante , e la prima frase della conclusione afferma "In questo articolo, abbiamo sostenuto che la modularità è la chiave per una programmazione di successo." Quindi la risposta a (1) è no.

Le funzioni ben progettate non presuppongono più i loro dati di quanto ne abbiano bisogno, quindi la parte relativa a "intimamente consapevole dei dati" è errata. (O almeno sbagliato come sarebbe OOP: non puoi programmare rigorosamente ad un alto livello di astrazione e ignorare tutti i dettagli per sempre in qualsiasi paradigma. Alla fine, una parte del programma ha effettivamente bisogno di sapere dettagli specifici dei dati.)

L'occultamento dei dati è un termine specifico OOP e non è esattamente la stessa cosa che nasconde l'informazione discussa nell'articolo. Le informazioni nascoste nell'articolo riguardano decisioni di design difficili da stabilire o che potrebbero cambiare. Non tutte le decisioni di progettazione relative a un formato di dati sono difficili o suscettibili di modifiche e non tutte le decisioni che è difficile o probabile modificare riguardano un formato di dati. Personalmente, non vedo perché i programmatori OO vogliano che tutto sia un oggetto. A volte, una semplice struttura dati è tutto ciò di cui hai bisogno.

Modifica: Ho trovato una citazione pertinente da un'intervista con Rich Hickey .

Fogus: Following that idea—some people are surprised by the fact that Clojure does not engage in data-hiding encapsulation on its types. Why did you decide to forgo data-hiding?

Hickey: Let’s be clear that Clojure strongly emphasizes programming to abstractions. At some point though, someone is going to need to have access to the data. And if you have a notion of “private”, you need corresponding notions of privilege and trust. And that adds a whole ton of complexity and little value, creates rigidity in a system, and often forces things to live in places they shouldn’t. This is in addition to the other losing that occurs when simple information is put into classes. To the extent the data is immutable, there is little harm that can come of providing access, other than that someone could come to depend upon something that might change. Well, okay, people do that all the time in real life, and when things change, they adapt. And if they are rational, they know when they make a decision based upon something that can change that they might in the future need to adapt. So, it’s a risk management decision, one I think programmers should be free to make. If people don’t have the sensibilities to desire to program to abstractions and to be wary of marrying implementation details, then they are never going to be good programmers.

    
risposta data 05.10.2013 - 12:17
fonte
11

... and is probably one of those articles that OOP was based on.

Non proprio, ma è stato aggiunto alla discussione, specialmente ai professionisti che, all'epoca, erano addestrati a decomporre i sistemi usando i primi criteri che descriveva sulla carta.

First I want to know if my assessment is correct. Does the FP paradigm and this article philosophically disagree?

No. Inoltre, ai miei occhi, la tua descrizione di come appare un programma FP non è diversa da quella che utilizza procedure o funzioni:

Data gets passed from function to function, each function being intimately aware of the data and "changing it" along the way.

... tranne per la parte "intimacy", dal momento che puoi (e spesso fai) avere funzioni che operano su dati astratti, proprio per evitare l'intimità. Quindi, hai un certo controllo su quella "intimità" e puoi regolarla come preferisci, impostando le interfacce (cioè le funzioni) per ciò che vuoi nascondere.

Quindi, non vedo alcun motivo per cui non saremmo in grado di seguire i criteri di Parnas di informazione che si nascondono usando la programmazione funzionale e finiamo con un'implementazione di un indice KWIC con simili vantaggi puntuali come sua seconda implementazione.

Assuming they agree, I'd like to know what FPs implementation of data hiding is. It's obvious to see this in OOP. You can have a private field that nobody outside the class can access. There's no obvious analogy of this to me in FP.

Per quanto riguarda i dati, è possibile elaborare astrazioni di dati e astrazioni di dati utilizzando FP. Ognuna di queste nasconde strutture e manipolazioni concrete di queste strutture in calcestruzzo utilizzando funzioni come astrazioni.

Modifica

C'è un numero crescente di asserzioni che affermano che "nascondere i dati" nel contesto di FP non è così utile (o OOP-ish (?)). Quindi, lasciatemi qui un esempio molto semplice e chiaro di SICP:

Supponiamo che il tuo sistema abbia bisogno di lavorare con numeri razionali. Un modo in cui potresti volerli rappresentare è una coppia o un elenco di due numeri interi: il numeratore e il denominatore. Così:

(define my-rat (cons 1 2)) ; here is my 1/2 

Se ignori l'astrazione dei dati, molto probabilmente otterrai il numeratore e il denominatore usando car e cdr :

(... (car my-rat)) ; do something with the numerator

Seguendo questo approccio, tutte le parti del sistema che manipolano i numeri razionali sapranno che un numero razionale è un cons - faranno cons di numeri per creare razionali ed estrarli usando gli operatori di lista.

Un problema che potresti incontrare è quando devi avere una forma ridotta dei numeri razionali - le modifiche saranno richieste in tutto il sistema. Inoltre, se decidi di ridurre al momento della creazione, potresti scoprire in seguito che ridurre quando accedi a uno dei termini razionali è migliore, producendo un altro cambiamento completo.

Un altro problema è se, per ipotesi, è preferibile una rappresentazione alternativa per loro e tu decidi di abbandonare la rappresentazione cons - di nuovo il cambiamento di fondo scala.

Qualunque sforzo sano nel gestire queste situazioni probabilmente inizierà a nascondere la rappresentazione dei razionali dietro le interfacce. Alla fine, si potrebbe finire con qualcosa di simile:

  • (make-rat <n> <d>) restituisce il numero razionale il cui numeratore è l'intero <n> e il cui denominatore è l'intero <d> .

  • (numer <x>) restituisce il numeratore del numero razionale <x> .

  • (denom <x>) restituisce il denominatore del numero razionale <x> .

e il sistema non sarà più (e non dovrebbe più) sapere di cosa sono fatti i razionali. Questo perché cons , car e cdr non sono intrinseci ai razionali, ma make-rat , numer e denom sono . Ovviamente, questo potrebbe facilmente essere un sistema FP. Quindi, "nascondere i dati" (in questo caso, meglio conosciuto come astrazione dei dati, o lo sforzo di incapsulare rappresentazioni e strutture concrete) si presenta come un concetto pertinente e una tecnica ampiamente utilizzata ed esplorata, sia nel contesto di OO, programmazione funzionale o qualunque sia.

E il punto è ... anche se si può cercare di fare distinzioni tra il "tipo di nascondimento" o l'incapsulazione che stanno facendo (se nascondono una decisione progettuale, o strutture dati o algoritmi - nel caso di procedurale astrazioni), tutti hanno lo stesso tema: sono motivati da uno o più punti Parnas resi espliciti. Cioè:

  • Changeability: se le modifiche richieste possono essere apportate localmente o diffuse attraverso il sistema.
  • Sviluppo indipendente: in che misura due parti del sistema possono essere sviluppate in parallelo.
  • Comprensibilità: quanta parte del sistema deve essere nota per comprendere una delle sue parti.

L'esempio sopra è stato preso dal libro SICP quindi, per la discussione completa e la presentazione di questi concetti nel libro, consiglio vivamente di dare un'occhiata a capitolo 2 . Raccomando anche di familiarizzare con i tipi di dati astratti nel contesto di FP, che porta altri problemi al tavolo.

    
risposta data 30.04.2015 - 19:37
fonte
8

La tua convinzione che la programmazione funzionale manchi di nascondere i dati è sbagliata. Basta un approccio diverso per nascondere i dati. Uno dei modi più comuni di nascondere i dati nella programmazione funzionale è attraverso l'uso di funzioni polimorfiche che prendono una funzione come argomento. Ad esempio, questa funzione

map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs

può vedere solo la struttura più esterna dei dati (cioè che è una lista) non può vedere nulla sui dati che l'elenco contiene e può operare solo sui dati attraverso la singola funzione che gli viene passata.

La funzione passata come argomento è analoga a un metodo pubblico sul tipo di dati che contiene l'elenco. Fornisce un modo limitato di operare sui dati, ma non espone il funzionamento interno del tipo di dati.

    
risposta data 07.10.2013 - 15:39
fonte
5

Qui c'è un po 'di paradosso. Anche se la programmazione funzionale si concentra su, bene, funzioni e frequentemente ha funzioni che funzionano direttamente su tipi di dati primitivi, tende ad avere più dati nascosti rispetto alla programmazione orientata agli oggetti.

Com'è così? Pensa a una bella interfaccia OO che nasconde i dati sottostanti - forse raccolte (sto cercando di scegliere qualcosa di quasi onnipresente). Potrebbe non essere necessario conoscere il tipo sottostante degli oggetti nella raccolta o nel tipo dell'oggetto che implementa la raccolta, purché si sappia che la collezione implementa, per esempio, IEnumerable. Quindi hai nascosto i dati.

Nella programmazione funzionale, potresti scrivere una funzione che funziona efficacemente con un'interfaccia IEnumerable, ma opera su un tipo di dati primitivo (o su qualsiasi tipo di dati). Ma cosa succede se il tipo non ha mai implementato i metodi IEnumerable? Ecco la chiave, puoi sempre avere i "metodi" che formano i pezzi necessari dell '"interfaccia" come parametri passati alla tua funzione. Oppure puoi mettere le funzioni insieme ai dati e fare le cose in un Modo simil-OO.

Si noti che in entrambi i casi non si hanno meno dati nascosti di quelli che si hanno in OO. La mia funzione generale che funziona su qualsiasi tipo chiaramente non sta accedendo ai dati in quel tipo - che avviene all'interno delle funzioni passate come parametri alla funzione generale, ma la funzione generale non fa mai capolino all'interno di quelle funzioni per vedere i dati.

Quindi, per quanto riguarda il punto 1, non penso che FP e l'articolo siano davvero in disaccordo. Non penso che la tua caratterizzazione di FP non nascondendo i dati sia corretta. Si potrebbe implementare il design che l'autore ha preferito in FP, certamente.

Per quanto riguarda il punto 4 (2 e 3 non ha senso rispondere dato quello che ho detto per il punto 1) varia. Varia anche nelle lingue OO e in molti campi privati sono privati per convenzione anziché applicati dalla lingua.

    
risposta data 11.10.2013 - 01:48
fonte
5

Ho intenzione di colpire un arto qui e dire che il concetto non è rilevante in FP come è in OO.

tl; dr; Il punto di occultamento dei dati è garantire che le responsabilità vengano mantenute dove dovrebbero essere, e non si hanno attori esterni che manipolano dati per i quali non sono a conoscenza. In FP i dati sono generati da espressioni, e in questo modo non puoi fare confusione con i dati perché non sono proprietà mutabili tanto quanto calcoli computabili che cambiano completamente le regole del gioco.

Nelle mie esperienze con FP; che sono certamente insignificanti, io tendo a trovare un netto contrasto con OO in ciò che denota una buona / comune modellazione dei dati.

Questo contrasto è che in OO in generale si modellano le cose per rappresentare i propri dati. Analogia automobilistica obbligatoria:

OO

  • Hai un oggetto auto, che nasconde correttamente i dettagli della macchina come l'implementazione CA (è azionata da una cinghia o azionata dalla pressione dell'aria? I consumatori non dovrebbero aver bisogno di saperlo, quindi nascondila).
  • Questo oggetto auto ha molte proprietà e metodi che delineano tutti i fatti riguardanti l'auto, nonché i modi in cui puoi lavorare con una macchina.
  • Questo oggetto auto ha proprietà che sono componenti di un'auto che nascondono ulteriormente dalla Car in generale le loro particolari implementazioni e i loro dati che permettono ai componenti della vettura di essere intercambiabili.

La cosa da notare qui è che quando modellate le cose in un formato OO, si tratta di rappresentare le cose come dati. Hai oggetti con proprietà, molte di queste proprietà sono oggetti con più proprietà. Esistono un paio di metodi qui e là collegati a questi oggetti, ma tutto ciò che fanno realmente è di solito il jigger delle proprietà degli oggetti in questo modo e quello, di nuovo è una modellazione molto incentrata sui dati; questo è il modello con cui i dati devono essere interagiti concentrandosi sulla strutturazione per rendere disponibili tutti i punti dei dati in modo che i consumatori possano modificare i dati in questo modo e quello.

FP

  • Hai una grande quantità di calcoli che ti permettono di descrivere i comportamenti
  • Queste espressioni di comportamento sono correlate in un modo che può essere tradotto nel modo in cui i comportamenti di un'auto sono correlati l'uno all'altro, come un'auto che ha accelerazione / decelerazione, ci sono due comportamenti che si oppongono a vicenda in un modo simile. li>

La grande differenza tra OO e FP che mi colpisce costantemente è come ho detto sopra il modo in cui modellizzate i dati. In OO come accennato sopra modellate i dati come dati, in FP modellizzate i dati come calcoli, espressioni, algoritmi, è più sulla modellazione delle attività dei vostri dati che sui fatti. Pensa alla modellazione dei dati di base in matematica, si tratta sempre di ottenere un'equazione in grado di generare i tuoi dati, che modellano i dati come l'attività che li causa, a differenza di OO la modellazione sta creando un modo per rappresentare i dati che hai. Questo è molto della distinzione tra FP e OO.

Ricorda, per molto tempo LISP, uno dei linguaggi FP fondamentali, ha vissuto con una quantità molto piccola di tipi di dati primitivi. Funziona perché l'approccio non riguarda la modellazione di rappresentazioni complesse dei dati tanto quanto i calcoli che generano ed esprimono i comportamenti del tuo sistema.

Quando inizio a scrivere codice in FP, comincio scrivendo codice che fa qualcosa, dove come quando inizio a scrivere codice in OO, inizio scrivendo modelli che descrivono qualcosa. Il fare delle cose è nascosto nelle FP in quanto espressioni, il fare delle cose è esposto in OO essendo descritto con i dati, nascondendo questi limiti di dati a detta esposizione.

Tornando alla domanda in questione, cosa dice FP riguardo l'occultamento dei dati, lo apprezza o non è d'accordo o no?

Dico che non importa, in OO i tuoi dati sono l'intestino e pezzi importanti del tuo programma che dovrebbero essere nascosti dall'essere intromessi. In FP il coraggio e la conoscenza del tuo sistema sono tutti nascosti negli algoritmi e nei calcoli che esprimono il sistema. Questi sono per definizione più o meno immutabili, l'unico modo per mutare le espressioni di computazione sono cose come i macro, ma anche in questo caso le definizioni delle mutazioni sono espressioni che non possono essere ulteriormente intromesse.

    
risposta data 07.10.2013 - 17:33
fonte
3

Innanzitutto, grazie per il link a questo fantastico articolo, non lo sapevo fino ad ora, e mi ha dato un grande contributo su alcune cose che stavo discutendo con altri progettisti di software della community negli ultimi anni. Ecco la mia opinione al riguardo:

First I want to know if my assessment is correct. Does the FP paradigm and this article philosophically disagree?

Il design di FP si concentra molto sul flusso di dati (che è IMHO non così male come può suggerire l'articolo). Se questo è un completo "disaccordo" è discutibile.

Assuming they disagree, how does FP "compensate" for its lack of data hiding? Perhaps they sacrifice data hiding but gain X, Y and Z. I'd like to know the reasoning for why X, Y and Z are considered more beneficial than data hiding.

IMHO non compensa. Vedi sotto.

Or, assuming they disagree, perhaps FP feels that data hiding is bad. If so, why does it think data hiding is bad?

Non credo che la maggior parte degli utenti o dei progettisti di FP si sentano o pensino in questo modo, vedi sotto.

Assuming they agree, I'd like to know what FPs implementation of data hiding is. It's obvious to see this in OOP. You can have a private field that nobody outside the class can access. There's no obvious analogy of this to me in FP.

Ecco il punto: probabilmente avete visto così tanti sistemi OOP implementati in modo non funzionale che ritenete che OOP non sia funzionale. E questo è un errore, IMHO OOP e FP sono per lo più concetti ortogonali, e puoi costruire perfettamente sistemi OO funzionali, che ti danno una risposta ovvia alla tua domanda. La classica implementazione di "oggetti" in FP viene eseguita utilizzando le chiusure e se si desidera che gli oggetti vengano utilizzati in un sistema funzionale , il punto chiave è di progettarli immutabili.

Quindi per creare sistemi più grandi, IMHO è possibile creare moduli, classi e oggetti usando concetti OO, esattamente come descritto in "Modularizzazione 2" nell'articolo senza uscire dal "percorso FP". Utilizzerai il concetto di modulo del tuo linguaggio FP preferito, rendi immutabili tutti i tuoi oggetti e usa il "meglio di entrambi i mondi".

    
risposta data 05.10.2013 - 10:21
fonte
3

TL; DR : No

Il paradigma del FP e questo articolo sono filosoficamente in disaccordo?.

No, non lo è. La programmazione funzionale è dichiarativa che è "uno stile di costruzione della struttura e degli elementi dei programmi per computer, che esprime la logica di un calcolo senza descriverne il controllo flusso. " È meno importante seguire il diagramma di flusso e più come creare regole che permettano al flusso di emergere da solo.

La programmazione procedurale è molto più vicina alla codifica di un diagramma di flusso rispetto alla programmazione funzionale. Ne consegue che le trasformazioni che si verificano e le codifiche di tali trasformazioni in procedure che vengono eseguite in ordine, esattamente come descrive il flusso in un diagramma di flusso.

Whereas procedural languages model execution of the program as a sequence of imperative commands that may implicitly alter shared state, functional programming languages model execution as the evaluation of complex expressions that only depend on each other in terms of arguments and return values. For this reason, functional programs can have a freer order of code execution, and the languages may offer little control over the order in which various parts of the program are executed. (For example, the arguments to a procedure invocation in Scheme are executed in an arbitrary order.)

Nascondere i dati

  • La programmazione funzionale ha i suoi metodi di occultamento dei dati, ad esempio, pensa chiusure . Questo è il dato che si nasconde per incapsulamento in una chiusura. È difficile per i campi essere più dati privati che sono stati chiusi perché solo la chiusura ha un riferimento ai dati e non è possibile fare riferimento ad essa esterna alla chiusura.
  • Uno dei motivi per nascondere i dati è quello di stabilizzare l'interfaccia di programmazione nascondendo i dati mutanti. La programmazione funzionale non ha dati mutanti, quindi non ha bisogno di nascondere il maggior numero di dati.
risposta data 06.10.2013 - 01:41
fonte