In che modo i linguaggi puramente funzionali gestiscono la modularità?

22

Vengo da uno sfondo orientato agli oggetti in cui ho imparato che le classi sono o almeno possono essere utilizzate per creare uno strato di astrazione che consente un facile riciclo del codice che può quindi essere utilizzato per creare oggetti o essere utilizzato in eredità.

Come per esempio posso avere una classe animale e poi da quella ereditare gatti e cani e in modo che tutti ereditino molti degli stessi tratti, e da quelle sottoclassi posso quindi creare oggetti che possono specificare una razza di animale o anche il nome di esso.
Oppure posso usare le classi per specificare più istanze dello stesso codice che gestisce o contiene cose leggermente diverse; come i nodi in una struttura di ricerca o più connessioni di database diverse e quali no.

Recentemente mi sono spostato nella programmazione funzionale, quindi stavo iniziando a chiedermi:
In che modo i linguaggi puramente funzionali gestiscono cose del genere? Cioè, lingue senza alcun concetto di classi e oggetti.

    
posta Electric Coffee 09.06.2013 - 20:23
fonte

5 risposte

18

Molte lingue funzionali hanno un sistema di moduli. (Tra l'altro, anche molti linguaggi orientati agli oggetti fanno). Ma anche in assenza di uno, puoi usare le funzioni come moduli.

JavaScript è un buon esempio. In JavaScript, le funzioni vengono utilizzate sia per implementare moduli sia per l'incapsulamento orientato agli oggetti. In Scheme, che è stata la principale fonte di ispirazione per JavaScript, ci sono le funzioni solo . Le funzioni sono utilizzate per implementare quasi tutto: oggetti, moduli (chiamati unità in Racket), persino strutture dati.

OTOH, Haskell e la famiglia ML hanno un sistema di moduli esplicito.

L'orientamento dell'oggetto riguarda l'astrazione dei dati. Questo è tutto. Modularità, ereditarietà, polimorfismo, anche lo stato mutabile sono preoccupazioni ortogonali.

    
risposta data 09.06.2013 - 20:56
fonte
4

Sembra che tu stia facendo due domande: "Come puoi ottenere la modularità nei linguaggi funzionali?" che è stato trattato in altre risposte e "come si possono creare astrazioni in linguaggi funzionali?" a cui risponderò.

Nelle lingue OO, tendi a concentrarti sul sostantivo, "un animale", "il mailserver", "la sua forchetta da giardino", ecc. I linguaggi funzionali, al contrario, enfatizzano il verbo "camminare", "per Recupera mail "," per prod ", ecc.

Non sorprende, quindi, che le astrazioni nei linguaggi funzionali tendano al di sopra dei verbi o delle operazioni piuttosto che delle cose. Un esempio che cerco sempre quando cerco di spiegare è l'analisi. Nei linguaggi funzionali, un buon modo per scrivere parser è specificando una grammatica e poi interpretandola. L'interprete crea un'astrazione sul processo di analisi.

Un altro esempio concreto di questo è un progetto su cui stavo lavorando non molto tempo fa. Stavo scrivendo un database in Haskell. Avevo un 'linguaggio incorporato' per specificare le operazioni al livello più basso; per esempio, mi ha permesso di scrivere e leggere le cose dal supporto di memorizzazione. Avevo un altro "linguaggio incorporato" separato per specificare operazioni al più alto livello. Poi ho avuto, ciò che è essenzialmente un interprete, per convertire le operazioni dal livello superiore al livello inferiore.

Questa è una forma di astrazione notevolmente generale, ma non è l'unica disponibile nei linguaggi funzionali.

    
risposta data 10.06.2013 - 15:48
fonte
4

Sebbene la "programmazione funzionale" non implichi implicazioni di vasta portata per questioni di modularità, particolari linguaggi affrontano la programmazione in generale in modi diversi. Il riutilizzo del codice e l'astrazione interagiscono in quanto meno si espone, più è difficile riutilizzare il codice. Mettendo da parte l'astrazione, affronterò due problemi di riusabilità.

Linguaggi OOP tipizzati staticamente, tradizionalmente utilizzati sottotitoli nominali, il che significa che un codice progettato per classe / modulo / interfaccia A può trattare solo con classe / modulo / interfaccia B quando B menziona esplicitamente A. Lingue nella famiglia di programmazione funzionale usano principalmente sottotipo strutturale , il che significa che il codice progettato per A può gestire B ogni volta che B ha tutti i metodi e / o i campi di A. B potrebbe essere stato creato da una squadra diversa prima che ci fosse bisogno di una classe / interfaccia più generale A. Ad esempio in OCaml , la sottotipizzazione strutturale si applica al sistema di moduli, al sistema di oggetti di tipo OOP e ai suoi tipi di varianti polimorfici piuttosto unici.

La differenza più importante tra OOP e FP wrt. la modularità è che l'unità predefinita in OOP raggruppa insieme come oggetto varie operazioni sullo stesso caso di valori, mentre l'unità predefinita in FP raggruppa insieme come funzione la stessa operazione per vari casi di valori. In FP è ancora molto facile raggruppare le operazioni insieme, ad esempio come moduli. (A proposito, né Haskell né F # hanno un vero e proprio sistema di moduli della famiglia ML.) Il Problema di espressione è il compito di aggiungere in modo incrementale entrambe le nuove operazioni che funzionano su tutti i valori (ad es. oggetti) e nuovi casi di valori che tutte le operazioni dovrebbero supportare (ad es. aggiungere una nuova classe con la stessa interfaccia). Come discusso nella prima lezione di Ralf Laemmel (che ha ampi esempi in C #), aggiungere nuove operazioni è problematico nei linguaggi OOP.

La combinazione di OOP e FP in Scala potrebbe renderlo uno dei più potenti linguaggi di wrt. modularità. Ma OCaml è ancora la mia lingua preferita e nel mio personale parere soggettivo non sfiora Scala. Le due conferenze di Ralf Laemmel illustrano la soluzione al problema delle espressioni in Haskell. Penso che questa soluzione, benché perfettamente funzionante, renda difficile l'utilizzo dei dati risultanti con il polimorfismo parametrico. Risolvere il problema delle espressioni con varianti polimorfiche in OCaml, spiegato nell'articolo di Jaques Garrigue collegato sotto, non ha questa lacuna. Collego anche capitoli di libri di testo che mettono a confronto gli usi della modularità non-OOP e OOP in OCaml.

Di seguito sono riportati i link specifici di Haskell e OCaml che si espandono sul Problema di espressione :

risposta data 10.06.2013 - 12:57
fonte
0

In realtà, il codice OO è molto meno riutilizzabile, e questo è in base alla progettazione. L'idea alla base di OOP è di limitare le operazioni su particolari parti di dati a determinati codici privilegiati che si trovano nella classe o nella posizione appropriata nella gerarchia dell'ereditarietà. Questo limita gli effetti avversi della mutabilità. Se una struttura dati cambia, ci sono solo tanti punti nel codice che possono essere responsabili.

Con l'immutabilità, non ti importa chi può operare su una data struttura dati, perché nessuno può cambiare la tua copia dei dati. Ciò rende molto più semplice la creazione di nuove funzioni per lavorare su strutture dati esistenti. Basta creare le funzioni e raggrupparle in moduli che sembrano appropriati dal punto di vista del dominio. Non devi preoccuparti di dove collocarli nella gerarchia dell'ereditarietà.

L'altro tipo di riutilizzo del codice sta creando nuove strutture dati per lavorare su funzioni esistenti. Questo viene gestito in linguaggi funzionali utilizzando funzionalità come generici e classi di tipi. Ad esempio, la classe di tipi Ord di Haskell ti consente di usa la funzione sort su qualsiasi tipo con un'istanza Ord . Le istanze sono facili da creare se non esistono già.

Prendi il tuo esempio di Animal e valuta l'implementazione di una funzione di alimentazione. L'implementazione OOP semplice consiste nel mantenere una collezione di oggetti Animal e nel loro scorrere tutti, chiamando il metodo feed su ciascuno di essi.

Tuttavia, le cose si complicano quando si arriva ai dettagli. Un oggetto Animal sa naturalmente che tipo di cibo mangia e quanto serve per sentirsi pieno. non naturalmente sa dove viene conservato il cibo e quanto è disponibile, quindi un oggetto FoodStore è appena diventato una dipendenza di ogni Animal , sia come un campo dell'oggetto Animal o passato come parametro del metodo feed . In alternativa, per mantenere la classe Animal più coesa, potresti spostare feed(animal) sull'oggetto FoodStore , oppure potresti creare un abominio di una classe chiamata AnimalFeeder o alcuni di questi.

In FP, non vi è alcuna inclinazione per i campi di una Animal a rimanere sempre raggruppati, il che ha alcune implicazioni interessanti per la riusabilità. Supponiamo che tu abbia un elenco di Animal record, con campi come name , species , location , food type , food amount , ecc. Hai anche un elenco di FoodStore record con campi come location , food type e food amount .

Il primo passo nell'alimentazione potrebbe essere quello di mappare ciascuno di questi elenchi di record a liste di coppie (food amount, food type) , con numeri negativi per gli importi degli animali. È quindi possibile creare funzioni per fare ogni sorta di cose con queste coppie, come sommare le quantità di ogni tipo di cibo. Queste funzioni non appartengono perfettamente a un modulo Animal o a FoodStore , ma sono altamente riusabili da entrambi.

Finisci con un sacco di funzioni che fanno cose utili con [(Num A, Eq B)] che sono riutilizzabili e modulari, ma hai problemi a capire dove metterle o come chiamarle come gruppo. L'effetto è che i moduli FP sono più difficili da classificare, ma la classificazione è molto meno importante.

    
risposta data 07.01.2015 - 23:18
fonte
-1

Una delle soluzioni più diffuse è quella di suddividere il codice in moduli, ecco come avviene in JavaScript:

    media.podcast = (function(name) {
    var fileExtension = 'mp3';        

     function determineFileExtension() {
         console.log('File extension is of type ' + fileExtension);
     }

     return {
         download: function(episode) {
            console.log('Downloading ' + episode + ' of ' + name);
            determineFileExtension();
        }
    }    
}('Astronomy podcast'));

Il articolo completo che spiega questo modello in JavaScript , a parte questo ci sono molti altri modi per definire un modulo, come RequireJS , CommonJS , Google Closure. Un altro esempio è Erlang, in cui sono presenti entrambi moduli e comportamenti che applicano API e pattern, giocando un ruolo simile a Interfaces in OOP.

    
risposta data 10.06.2013 - 15:20
fonte

Leggi altre domande sui tag