La programmazione funzionale è un superset di oggetti orientati?


26

Più programmazione funzionale faccio, più mi sento come se aggiungesse un ulteriore livello di astrazione che assomiglia a come è uno strato di cipolla, che comprende tutti gli strati precedenti.

Non so se questo sia vero, quindi andando fuori dai principi OOP con cui ho lavorato per anni, qualcuno può spiegare come funzionale o non li descrive in modo accurato: incapsulamento, astrazione, ereditarietà, polimorfismo

Penso che tutti possiamo dire, sì, ha l'incapsulamento tramite le tuple o le tuple contano tecnicamente come un fatto di "programmazione funzionale" o sono solo un'utilità del linguaggio?

So che Haskell può soddisfare i requisiti di "interfacce", ma ancora una volta non è sicuro se il suo metodo sia un fatto funzionale? Immagino che il fatto che i funzionali abbiano una base matematica si possa dire che sono sicuramente definiti nell'aspettativa funzionale?

Per favore, descrivi in ​​dettaglio come ritieni funzionale o meno ai 4 principi di OOP.

Modifica: capisco bene le differenze tra il paradigma funzionale e il paradigma orientato agli oggetti e mi rendo conto che al giorno d'oggi ci sono molti linguaggi multiparadigm che possono fare entrambe le cose. Sto davvero cercando delle definizioni di come il fp (pensa purista, come l'hakell) possa fare una delle 4 cose elencate, o perché non può farne nessuna. vale a dire "L'incapsulamento può essere fatto con le chiusure" (o se sbaglio in questa convinzione, si prega di indicare il perché).


7
Quei 4 principi non "fanno" OOP. OOP semplicemente "risolve" quelli usando le classi, la classe hiearchy e le loro istanze. Ma anch'io vorrei una risposta se ci sono modi per raggiungerli nella programmazione funzionale.
Euforico,

2
@Euforico A seconda della definizione, rende OOP.
Konrad Rudolph,

2
@KonradRudolph So che molte persone sostengono queste cose e i vantaggi che apportano come proprietà uniche di OOP. Supponendo che "polimorfismo" significhi "polimorfismo del sottotipo", posso andare con gli ultimi due come parte integrante di OOP. Ma devo ancora trovare un'utile definizione di incapsulamento e astrazione che escluda approcci decisamente non-OOP. Puoi nascondere i dettagli e limitarne l'accesso anche in Haskell. E l'Haskell ha anche un polimorfismo ad hoc, non solo un polimorfismo del sottotipo - la domanda è: il bit del "sottotipo" conta?

1
@KonradRudolph Questo non lo rende più accettabile. Semmai, è un incentivo intensificare e dare a coloro che lo diffondono motivo di riconsiderarlo.

1
L'astrazione è intrinseca a qualsiasi programmazione, almeno a qualsiasi programmazione oltre il codice macchina grezzo. L'incapsulamento esisteva molto prima di OOP ed è intrinseco alla programmazione funzionale. Non è richiesto un linguaggio funzionale per includere la sintassi esplicita per ereditarietà o polimorfismo. Immagino che si sommi a "no".
sdenham,

Risposte:


44

La programmazione funzionale non è un livello sopra OOP; è un paradigma completamente diverso. È possibile fare OOP in uno stile funzionale (F # è stato scritto esattamente per questo scopo), e dall'altra parte dello spettro hai cose come Haskell, che rifiuta esplicitamente i principi dell'orientamento agli oggetti.

Puoi fare incapsulamento e astrazione in qualsiasi lingua abbastanza avanzata da supportare moduli e funzioni. OO fornisce meccanismi speciali per l'incapsulamento, ma non è qualcosa inerente a OO. Il punto di OO è la seconda coppia che hai citato: eredità e polimorfismo. Il concetto è formalmente noto come sostituzione di Liskov e non è possibile ottenerlo senza il supporto a livello di linguaggio per la programmazione orientata agli oggetti. (Sì, è possibile simularlo in alcuni casi, ma perdi molti dei vantaggi che OO porta sul tavolo.)

La programmazione funzionale non si concentra sulla sostituzione di Liskov. Si concentra sull'aumento del livello di astrazione e sulla riduzione al minimo dell'uso di uno stato mutevole e delle routine con "effetti collaterali", che è un termine che i programmatori funzionali amano usare per creare routine che effettivamente fanno qualcosa (anziché semplicemente calcolare qualcosa) spaventoso. Ma ancora una volta, sono paradigmi completamente separati, che possono essere usati insieme o meno, a seconda della lingua e delle capacità del programmatore.


1
Bene, l'ereditarietà (in quei casi eccezionalmente rari quando è necessaria) è realizzabile rispetto alla composizione ed è più pulita dell'eredità a livello di tipo. Il polimorfismo è naturale, soprattutto in presenza di tipi polimorfici. Ma ovviamente concordo sul fatto che FP non ha nulla a che fare con OOP e i suoi principi.
SK-logic

È sempre possibile simularlo: puoi implementare oggetti in qualsiasi lingua tu scelga. Sono d'accordo con tutto il resto però :)
Eliot Ball

5
Non penso che il termine "effetti collaterali" sia stato coniato (o utilizzato principalmente) dai programmatori funzionali.
sepp2k,

4
@ sepp2k: non ha detto che hanno inventato il termine, solo che lo hanno usato con lo stesso tono di quello che si userebbe normalmente per riferirsi ai bambini che si rifiutano di scendere dal prato.
Aaronaught,

16
@Aaronaught non sono i bambini sul mio prato che mi danno fastidio, sono i loro sanguinosi effetti collaterali! Se smettessero di mutare in tutto il mio prato non mi dispiacerebbe affatto.
Jimmy Hoffa,

10

Trovo utile la seguente intuizione per confrontare OOP e FP.

Invece di considerare FP come un superset di OOP, pensa a OOP e FP come due modi alternativi di guardare un modello di calcolo sottostante simile in cui hai:

  1. Alcune operazioni che vengono eseguite,
  2. alcuni argomenti di input per l'operazione,
  3. alcuni dati / parametri fissi che possono influenzare la definizione dell'operazione,
  4. un certo valore di risultato e
  5. forse un effetto collaterale.

In OOP questo viene catturato da

  1. Un metodo che viene eseguito,
  2. gli argomenti di input di un metodo,
  3. l'oggetto su cui viene invocato il metodo, contenente alcuni dati locali sotto forma di variabili membro,
  4. il valore di ritorno del metodo (possibilmente nullo),
  5. gli effetti collaterali del metodo.

In FP questo viene catturato da

  1. Una chiusura che viene eseguita,
  2. gli argomenti di input della chiusura,
  3. le variabili catturate della chiusura,
  4. il valore di ritorno della chiusura,
  5. i possibili effetti collaterali della chiusura (in linguaggi puri come Haskell, ciò avviene in modo molto controllato).

Con questa interpretazione, un oggetto può essere visto come una raccolta di chiusure (i suoi metodi) che catturano tutte le stesse variabili non locali (le variabili membro dell'oggetto comuni a tutte le chiusure nella raccolta). Questa visione è anche supportata dal fatto che nelle lingue orientate agli oggetti le chiusure sono spesso modellate come oggetti con esattamente un metodo.

Penso che i diversi punti di vista derivino dal fatto che la vista orientata agli oggetti è centrata sugli oggetti (i dati) mentre la vista funzionale è centrata sulle funzioni / chiusure (le operazioni).


8

Dipende da chi chiedi una definizione di OOP. Chiedi a cinque persone e probabilmente otterrai sei definizioni. Wikipedia dice :

I tentativi di trovare una definizione di consenso o una teoria dietro gli oggetti non si sono rivelati molto efficaci

Quindi ogni volta che qualcuno dà una risposta molto definitiva, prendila con un granello di sale.

Detto questo, c'è una buona argomentazione da sostenere che, sì, FP è un superset di OOP come paradigma. In particolare, la definizione di Alan Kay del termine programmazione orientata agli oggetti non contraddice questa nozione (ma lo fa Kristen Nygaard ). Tutto ciò di cui Kay si preoccupava davvero era che tutto fosse un oggetto e che la logica fosse implementata passando messaggi tra oggetti.

Forse più interessante per la tua domanda, le classi e gli oggetti possono essere pensati in termini di funzioni e chiusure restituite da funzioni (che fungono da classi e costruttori contemporaneamente). Questo si avvicina molto alla programmazione basata su prototipi, e in effetti JavaScript consente proprio di farlo.

var cls = function (x) {
    this.y = x;
    this.fun = function () { alert(this.y); };
    return this;
};

var inst = new cls(42);
inst.fun();

(Naturalmente JavaScript consente valori mutanti che sono illegali nella programmazione puramente funzionale ma non è richiesto in una definizione rigorosa di OOP.)

La domanda più importante è: si tratta di una classificazione significativa di OOP? È utile pensarlo come un sottoinsieme della programmazione funzionale? Penso che nella maggior parte dei casi non lo sia.


1
Sento che può essere significativo pensare a dove dovrebbe essere tracciata la linea quando cambio paradigmi. Se si dice che non c'è modo di ottenere il polimorfismo subtitale in fp, allora non mi preoccuperò mai di provare a usare fp nel modellare qualcosa che si adatterebbe bene con esso. Se tuttavia è possibile, potrei dedicare del tempo per ottenere un buon modo di farlo (anche se un buon modo potrebbe non essere possibile) quando si lavora pesantemente in uno spazio fp ma si desidera il polimorfismo subtitale in alcuni spazi di nicchia. Ovviamente se la maggior parte del sistema si adatta comunque, sarebbe meglio usare OOP.
Jimmy Hoffa,

6

FP come OO non è un termine ben definito. Ci sono scuole con definizioni diverse, a volte contrastanti. Se prendi quello che hanno in comune, scendi a:

  • la programmazione funzionale è la programmazione con funzioni di prima classe

  • La programmazione OO sta programmando con il polimorfismo dell'inclusione combinato con almeno una forma limitata di sovraccarico risolto dinamicamente. (Una nota a margine: nei circoli OO il polimorfismo è di solito considerato polimorfismo dell'inclusione , mentre nelle scuole FP di solito significa polimorfismo parametrico .)

Tutto il resto è presente altrove o assente in alcuni casi.

FP e OO sono due strumenti per costruire astrazioni. Ognuno di essi ha i propri punti di forza e di debolezza (ad esempio hanno una diversa direzione di estensione preferita nel problema dell'espressione), ma nessuno è intrinsecamente più potente dell'altro. È possibile creare un sistema OO su un kernel FP (CLOS è uno di questi sistemi). È possibile utilizzare un framework OO per ottenere funzioni di prima classe (ad esempio, vedere il modo in cui le funzioni lambda sono definite in C ++ 11).


Penso che intendi "funzioni di prima classe" piuttosto che "funzioni di primo ordine".
dan_waterworth,

Errr ... C ++ 11 lambda non sono funzioni di prima classe: ogni lambda ha il suo tipo ad-hoc (per tutti gli scopi pratici, una struttura anonima), incompatibile con un tipo di puntatore di funzione nativa. E std::function, a cui è possibile assegnare sia i puntatori di funzione che i lambda, è decisamente generico, non orientato agli oggetti. Ciò non sorprende, poiché il limitato marchio di polimorfismo orientato agli oggetti (polimorfismo dei sottotipi) è strettamente meno potente del polimorfismo parametrico (persino Hindley-Milner, per non parlare dell'intero sistema F-omega).
pyon,

Non ho molta esperienza con linguaggi funzionali puristi ma se riesci a definire classi con un metodo statico all'interno di chiusure e passarle in contesti diversi, direi che (goffamente forse) almeno a metà strada lì su opzioni di stile funzionale. Esistono molti modi per aggirare parametri severi nella maggior parte delle lingue.
Erik Reppen,

2

No; OOP può essere visto come un superset di programmazione procedurale e differisce fondamentalmente dal paradigma funzionale perché ha lo stato rappresentato nei campi di istanza. Nel paradigma funzionale le variabili sono funzioni che vengono applicate ai dati costanti al fine di ottenere il risultato desiderato.

In realtà puoi considerare la programmazione funzionale un sottoinsieme di OOP; se rendi immutabili tutte le tue classi, potresti considerare di avere un qualche tipo di programmazione funzionale.


3
Le classi immutabili non eseguono funzioni di ordine superiore, comprensioni di elenchi o chiusure. Fp non è un sottoinsieme.
Jimmy Hoffa,

1
@Jimmy Hoffa: puoi simulare facilmente una funzione di oreder superiore creando una classe che ha un singolo metodo che accetta o più oggetti di un tipo simile e restituisce anche un oggetto di questo tipo simile (tipo che ha un metodo e nessun campo) . Il comperhension dell'elenco non è qualcosa correlato al linguaggio di programmazione, non al paradigma (Smalltalk lo supporta ed è OOP). Le chiusure sono presenti in C # e verranno inserite anche in Java.
m3th0dman,

sì C # ha chiusure, ma è perché è multi-paradigma, chiusure sono state aggiunte insieme ad altri pezzi FP in C # (per i quali sono eternamente grato) ma la loro presenza in un linguaggio oop non li rende oop. Un buon punto sulla funzione di ordine superiore, tuttavia, l'incapsulamento di un metodo in una classe consente lo stesso comportamento.
Jimmy Hoffa,

2
Sì, ma se usi chiusure per modificare lo stato, programmeresti comunque in un paradigma funzionale? Il punto è che il paradigma funzionale riguarda la mancanza di stato, non le funzioni di alto ordine, la ricorsione o le chiusure.
m3th0dman,

pensiero interessante sulla definizione di fp .. dovrò pensarci di più, grazie per aver condiviso le tue osservazioni.
Jimmy Hoffa,

2

Risposta:

Wikipedia ha un ottimo articolo sulla programmazione funzionale con alcuni degli esempi che chiedi. @Konrad Rudolph ha già fornito il link all'articolo OOP .

Non penso che un paradigma sia un superinsieme dell'altro. Sono diverse prospettive sulla programmazione e alcuni problemi sono meglio risolti da una prospettiva e alcuni da un'altra.

La tua domanda è ulteriormente complicata da tutte le implementazioni di FP e OOP. Ogni lingua ha le sue stranezze che sono rilevanti per qualsiasi buona risposta alla tua domanda.

Rambling sempre più tangenziale:

Mi piace l'idea che un linguaggio come Scala cerchi di darti il ​​meglio di entrambi i mondi. Temo che ti dia anche le complicazioni di entrambi i mondi.

Java è un linguaggio OO, ma la versione 7 ha aggiunto una funzione "prova con risorse" che può essere utilizzata per imitare una sorta di chiusura. Qui imita l'aggiornamento di una variabile locale "a" nel mezzo di un'altra funzione, senza renderla visibile a quella funzione. In questo caso la prima metà dell'altra funzione è il costruttore ClosureTry () e la seconda metà è il metodo close ().

public class ClosureTry implements AutoCloseable {

    public static void main(String[] args) {
        int a = 1;
        try(ClosureTry ct = new ClosureTry()) {
            System.out.println("Middle Stuff...");
            a = 2;
        }
        System.out.println("a: " + a);
    }

    public ClosureTry() {
        System.out.println("Start Stuff Goes Here...");
    }

    /** Interface throws exception, but we don't have to. */
    public void close() {
        System.out.println("End Stuff Goes Here...");
    }
}

Produzione:

Start Stuff Goes Here...
Middle Stuff...
End Stuff Goes Here...
a: 2

Questo potrebbe essere utile per lo scopo previsto di aprire uno stream, scrivere nello stream e chiuderlo in modo affidabile, o semplicemente associare due funzioni in modo da non dimenticare di chiamare la seconda dopo aver fatto un po 'di lavoro tra di loro . Certo, è così nuovo e insolito che un altro programmatore potrebbe rimuovere il blocco try senza rendersi conto che stanno rompendo qualcosa, quindi attualmente è una specie di anti-pattern, ma interessante che possa essere fatto.

Puoi esprimere qualsiasi ciclo nella maggior parte delle lingue imperative come ricorsione. Oggetti e variabili possono essere resi immutabili. Le procedure possono essere scritte per ridurre al minimo gli effetti collaterali (anche se direi che una vera funzione non è possibile su un computer: il tempo necessario per l'esecuzione e le risorse del processore / disco / sistema che consuma sono inevitabili effetti collaterali). Alcuni linguaggi funzionali possono essere fatti per fare molte se non tutte le operazioni orientate agli oggetti. Non devono escludersi a vicenda, sebbene alcune lingue presentino limitazioni (come non consentire alcun aggiornamento delle variabili) che impediscono determinati schemi (come i campi mutabili).

Per me, le parti più utili della programmazione orientata agli oggetti sono il nascondimento dei dati (incapsulamento), il trattamento di oggetti abbastanza simili come gli stessi (polimorfismo) e la raccolta di dati e metodi che operano insieme su tali dati (oggetti / classi). L'ereditarietà può essere il fiore all'occhiello di OOP, ma per me è la parte meno importante e meno utilizzata.

Le parti più utili della programmazione funzionale sono l'immutabilità (token / valori anziché variabili), funzioni (senza effetti collaterali) e chiusure.

Non penso che sia orientato agli oggetti, ma devo dire che una delle cose più utili dell'informatica è la capacità di dichiarare un'interfaccia, quindi avere varie funzionalità e dati implementare quell'interfaccia. Mi piace anche avere alcuni dati mutabili con cui lavorare, quindi credo di non essere completamente a mio agio in linguaggi esclusivamente funzionali, anche se cerco di limitare la mutabilità e gli effetti collaterali in tutti i miei progetti di programma.

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.