Database e programmazione funzionale sono in contrasto?


122

Sono uno sviluppatore web da un po 'di tempo e di recente ho iniziato a imparare un po' di programmazione funzionale. Come altri, ho avuto alcuni problemi significativi ad applicare molti di questi concetti al mio lavoro professionale. Per me, la ragione principale di ciò è che vedo un conflitto tra l'obiettivo di FP di rimanere apolidi sembra piuttosto in contrasto con il fatto che la maggior parte del lavoro di sviluppo web che ho svolto è stato fortemente legato ai database, che sono molto incentrati sui dati.

Una cosa che mi ha reso uno sviluppatore molto più produttivo sul lato OOP delle cose è stata la scoperta di mappatori relazionali a oggetti come MyGeneration d00dads per .Net, Class :: DBI per perl, ActiveRecord per ruby, ecc. Questo mi ha permesso di stare alla larga dallo scrivere istruzioni di inserimento e selezione tutto il giorno e concentrarsi sul lavorare facilmente con i dati come oggetti. Naturalmente, potevo ancora scrivere query SQL quando era necessaria la loro potenza, ma per il resto veniva astratta bene dietro le quinte.

Ora, passando alla programmazione funzionale, sembra che con molti framework web FP come i collegamenti richiedano la scrittura di molto codice sql boilerplate, come in questo esempio . Weblocks sembra un po 'meglio, ma sembra utilizzare una sorta di modello OOP per lavorare con i dati e richiede ancora che il codice venga scritto manualmente per ogni tabella nel database come in questo esempio . Suppongo che tu usi un po 'di generazione di codice per scrivere queste funzioni di mappatura, ma sembra decisamente poco lisp.

(Nota che non ho esaminato i Weblock oi Link in modo estremamente accurato, potrei semplicemente fraintendere il modo in cui vengono utilizzati).

Quindi la domanda è, per le porzioni di accesso al database (che credo siano piuttosto grandi) dell'applicazione web, o altro sviluppo che richiede un'interfaccia con un database sql, sembra che siamo costretti a seguire uno dei seguenti percorsi:

  1. Non utilizzare la programmazione funzionale
  2. Accedi ai dati in un modo fastidioso e non astratto che implica la scrittura manuale di un sacco di codice SQL o simile a SQL ala Links
  3. Forza il nostro linguaggio funzionale in un paradigma pseudo-OOP, rimuovendo così un po 'dell'eleganza e della stabilità della vera programmazione funzionale.

Chiaramente, nessuna di queste opzioni sembra l'ideale. Ha trovato un modo per aggirare questi problemi? C'è davvero un problema pari qui?

Nota: personalmente ho più familiarità con LISP sul fronte FP, quindi se vuoi fornire esempi e conoscere più lingue FP, lisp sarebbe probabilmente la lingua preferita di scelta

PS: per problemi specifici di altri aspetti dello sviluppo web, vedere questa domanda .



4
Dai un'occhiata a ClojureQL e HaskellDB. Sono strati di astrazione che utilizzano l'algebra relazionale.
Masse

10
Stai partendo con la premessa sbagliata. La programmazione funzionale riguarda la gestione dello stato in modo esplicito e corretto. Funzionano molto bene con i database, infatti.
Lucian

3
SQL è uno dei linguaggi focalizzati sulla programmazione funzionale di maggior successo, non credo che ci siano difficoltà intrinseche.
Douglas

3
Il tuo # 2 e # 3 sono una falsa dicotomia. La scrittura di SQL raw non è qualcosa che dovrebbe essere necessariamente evitato e le astrazioni su un database non devono necessariamente essere OOP-esque.
Dan Burton

Risposte:


45

Prima di tutto, non direi che CLOS (Common Lisp Object System) è "pseudo-OO". È OO di prima classe.

Secondo, credo che dovresti usare il paradigma che si adatta alle tue esigenze.

Non è possibile archiviare dati senza stato, mentre una funzione è un flusso di dati e non necessita di uno stato.

Se hai diverse esigenze mescolate, mescola i tuoi paradigmi. Non limitarti a utilizzare solo l'angolo inferiore destro della tua cassetta degli attrezzi.


3
solo per divertimento, ho pensato di menzionare datalog che tenta di essere un database più senza stato. registra tutte le azioni, come "l'utente piace il post 1233". Queste azioni si risolvono nel vero stato del database. La chiave è che le domande sono solo fatti piuttosto che mutazione ...
Chet

80

Arrivando a questo dal punto di vista di una persona che si occupa di database, trovo che gli sviluppatori front-end si sforzino troppo di trovare modi per adattare i database al loro modello piuttosto che considerare i modi più efficaci per utilizzare database che non siano orientati agli oggetti o funzionali ma relazionali e utilizzanti insiemistica. Ho visto questo risultato generalmente in codice con prestazioni scadenti. Inoltre, crea codice difficile da ottimizzare.

Quando si prende in considerazione l'accesso al database, ci sono tre considerazioni principali: integrità dei dati (perché tutte le regole aziendali dovrebbero essere applicate a livello di database non tramite l'interfaccia utente), prestazioni e sicurezza. SQL è stato scritto per gestire le prime due considerazioni in modo più efficace rispetto a qualsiasi linguaggio front-end. Perché è specificamente progettato per farlo. Il compito di un database è molto diverso dal compito di un'interfaccia utente. C'è da meravigliarsi che il tipo di codice più efficace nella gestione dell'attività sia concettualmente diverso?

E i database contengono informazioni fondamentali per la sopravvivenza di un'azienda. C'è da meravigliarsi che le aziende non siano disposte a sperimentare nuovi metodi quando è in gioco la loro sopravvivenza. Diamine, molte aziende non sono disposte a passare a nuove versioni del loro database esistente. Quindi c'è un intrinseco conservatorismo nella progettazione del database. Ed è deliberatamente in questo modo.

Non proverei a scrivere T-SQL o utilizzare concetti di progettazione del database per creare la tua interfaccia utente, perché dovresti provare a utilizzare il linguaggio dell'interfaccia e i concetti di progettazione per accedere al mio database? Perché pensi che SQL non sia abbastanza elegante (o nuovo)? O non ti senti a tuo agio? Solo perché qualcosa non si adatta al modello con cui ti senti più a tuo agio, non significa che sia cattivo o sbagliato. Significa che è diverso e probabilmente diverso per un motivo legittimo. Utilizzi uno strumento diverso per un'attività diversa.


"SQL è stato scritto per gestire le prime due considerazioni in modo più efficace di qualsiasi linguaggio front end." Oh veramente? Perché, allora, è che i vincoli SQL ancora non possono fare cose come questa ?
Robin Green

Ma un trigger può, questo è uno degli scopi principali dei trigger per essere in grado di gestire vincoli complessi.
HLGEM

2
Sposterei il tuo ultimo paragrafo in modo che sia il paragrafo principale. Punto molto positivo, che fa eco a ciò che anche gli altri stanno sollecitando, che è un approccio multi-paradigma (non unico per tutti).
pestofago

31

Dovresti guardare il documento "Out of the Tar Pit" di Ben Moseley e Peter Marks, disponibile qui: "Out of the Tar Pit" (6 febbraio 2006)

È un classico moderno che descrive in dettaglio un paradigma / sistema di programmazione chiamato Programmazione funzionale-relazionale. Pur non essendo direttamente correlato ai database, discute come isolare le interazioni con il mondo esterno (database, per esempio) dal nucleo funzionale di un sistema.

L'articolo discute anche come implementare un sistema in cui lo stato interno dell'applicazione è definito e modificato utilizzando un'algebra relazionale, che ovviamente è correlata ai database relazionali.

Questo documento non darà una risposta esatta su come integrare database e programmazione funzionale, ma aiuterà a progettare un sistema per ridurre al minimo il problema.


4
Che bel giornale. Il link che dai è interrotto (temporaneamente?), Ma ho trovato il documento anche su shaffner.us/cs/papers/tarpit.pdf
pestophagous

2
@queque Il link originale è ancora morto. Ho inserito il nuovo collegamento nella risposta di Kevin.
David Tonhofer

25
  1. I linguaggi funzionali non hanno l'obiettivo di rimanere apolidi, hanno l'obiettivo di rendere esplicita la gestione dello stato. Ad esempio, in Haskell, puoi considerare la monade di stato come il cuore dello stato "normale" e la monade IO una rappresentazione di stato che deve esistere al di fuori del programma. Entrambe queste monadi ti consentono di (a) rappresentare esplicitamente azioni stateful e (b) costruire azioni stateful componendole usando strumenti referenzialmente trasparenti.

  2. Fai riferimento a una serie di ORM che, in base al loro nome, astraggono database come insiemi di oggetti. In verità, questo non è ciò che rappresentano le informazioni in un database relazionale! Per il suo nome, rappresenta i dati relazionali. SQL è un'algebra (linguaggio) per gestire le relazioni su un set di dati relazionali ed è in realtà abbastanza "funzionale". Ne parlo in modo da considerare che (a) gli ORM non sono l'unico modo per mappare le informazioni del database, (b) che SQL è in realtà un linguaggio piuttosto carino per alcuni progetti di database e (c) che i linguaggi funzionali hanno spesso algebra relazionale mappature che espongono la potenza di SQL in modo idiomatico (e, nel caso di Haskell, tipechecked).

Direi che la maggior parte delle liscie sono il linguaggio funzionale di un uomo povero. È pienamente in grado di essere utilizzato secondo le pratiche funzionali moderne, ma poiché non li richiede, è meno probabile che la comunità li utilizzi. Ciò porta a una combinazione di metodi che possono essere molto utili ma certamente oscura come le interfacce funzionali pure possano ancora utilizzare i database in modo significativo.


15

Non credo che la natura senza stato dei linguaggi fp sia un problema con la connessione ai database. Lisp è un linguaggio di programmazione funzionale non puro, quindi non dovrebbe avere problemi a gestire lo stato. E i linguaggi di programmazione funzionali puri come Haskell hanno modi di gestire input e output che possono essere applicati all'utilizzo dei database.

Dalla tua domanda sembra che il tuo problema principale risieda nel trovare un buon modo per astrarre i dati basati sui record che ottieni dal tuo database in qualcosa che è lisp-y (lisp-ish?) Senza dover scrivere molto SQL codice. Questo sembra più un problema con gli strumenti / librerie che un problema con il paradigma del linguaggio. Se vuoi fare FP puro, forse lisp non è la lingua giusta per te. Il lisp comune sembra più l'integrazione di buone idee da oo, fp e altri paradigmi che non il puro fp. Forse dovresti usare Erlang o Haskell se vuoi seguire il percorso FP puro.

Penso che anche le idee 'pseudo-oo' in lisp abbiano il loro merito. Potresti provarli. Se non si adattano al modo in cui desideri lavorare con i tuoi dati, potresti provare a creare un livello sopra Weblock che ti consenta di lavorare con i tuoi dati nel modo desiderato. Potrebbe essere più facile che scrivere tutto da solo.

Disclaimer: non sono un esperto di Lisp. Sono principalmente interessato ai linguaggi di programmazione e ho giocato con Lisp / CLOS, Scheme, Erlang, Python e un po 'di Ruby. Nella vita di programmazione quotidiana sono ancora costretto a usare C #.


3
Erlang non è puro FP in nessuna definizione della parola. Scrivi erlang creando molti processi (tutti in esecuzione in parallelo) che si inviano messaggi l'un l'altro, proprio come gli oggetti in, diciamo, smalltalk. Quindi da una prospettiva di alto livello, potrebbe anche sembrare un po 'OO-ish, e sicuramente ha uno stato. Se ingrandisci (nel codice in esecuzione all'interno di un processo) sembra più funzionale, ma non è ancora puramente funzionale, poiché puoi inviare messaggi (e quindi eseguire operazioni di I / O, ecc.) Da qualsiasi luogo tu voglia, oltre a archiviarli " variabili globali "(globali al processo, all'interno di qualcosa chiamato" processo dict ".)
Amadiro

@Amadiro "sicuramente ha stato". Certo che lo fa. Abbiamo sempre lo stato. Il problema non è lo stato, è lo stato mutevole . Una buona parte della "programmazione funzionale" consiste nell'eliminare le rappresentazioni di stato che sono fragili (ad esempio, istanze di oggetti che vengono modificate da altri processi mentre teniamo un riferimento ad esse, in modo non transazionale per aggiungere la beffa al danno). Erlang ha uno stato non mutabile e quindi soddisfa questi criteri di programmazione funzionale. E quindi: database di qualsiasi tipo non sono mai un problema. Gli aggiornamenti del database sono un problema (vedi anche la brutta asserzione in Prolog).
David Tonhofer

15

Se il tuo database non distrugge le informazioni, puoi lavorarci in un modo funzionale coerente con i valori di programmazione "puramente funzionali" lavorando nelle funzioni dell'intero database come valore.

Se al momento T il database afferma che "A Bob piace Suzie", e avevi una funzione Mi piace che accettava un database e un liker, allora fintanto che puoi recuperare il database al momento T hai un programma funzionale puro che coinvolge un database . per esempio

# Start: Time T
likes(db, "Bob")
=> "Suzie"
# Change who bob likes
...
likes(db "Bob")
=> "Alice"
# Recover the database from T
db = getDb(T)
likes(db, "Bob")
=> "Suzie"

Per fare questo non puoi mai buttare via le informazioni che potresti usare (il che in tutta praticità significa che non puoi buttare via le informazioni), quindi le tue esigenze di archiviazione aumenteranno in modo monotono. Ma puoi iniziare a lavorare con il tuo database come una serie lineare di valori discreti, dove i valori successivi sono correlati a quelli precedenti tramite transazioni.

Questa è l'idea principale dietro Datomic , per esempio.


Bello. Non sapevo nemmeno di Datomic. Vedi anche: rationale for Datomic .
David Tonhofer

12

Affatto. Esiste un genere di database noti come "Database funzionali", di cui Mnesia è forse l'esempio più accessibile. Il principio di base è che la programmazione funzionale è dichiarativa, quindi può essere ottimizzata. Puoi implementare un join utilizzando List Comprehensions su raccolte persistenti e Query Optimiser può elaborare automaticamente come implementare l'accesso al disco.

Mnesia è scritto in Erlang e per quella piattaforma è disponibile almeno un framework web ( Erlyweb ). Erlang è intrinsecamente parallelo a un modello di threading nulla condiviso, quindi in un certo senso si presta ad architetture scalabili.


1
Non penso che sia una grande soluzione. Esistono anche database orientati agli oggetti, ma in genere si desidera connettersi a un semplice database SQL relazionale vecchio.
jalf

4
Hai ancora una mancata corrispondenza di impedenza con linguaggi e database OO più o meno allo stesso modo in cui hai una mancata corrispondenza di impedenza SQL funzionale.
ConcernedOfTunbridgeWells

1
@ConcernedOfTunbridgeWells Affermerò arbitrariamente che questo disadattamento di impedenza è frutto dell'immaginazione di persone con martelli che hanno bisogno di tutto per essere chiodi. Strati molto sottili e la conoscenza di SQL possono fare elegantemente molto, quindi jOOQ e spessori simili.
David Tonhofer

6

Un database è il modo perfetto per tenere traccia dello stato in un'API senza stato. Se ti iscrivi a REST, il tuo obiettivo è scrivere codice senza stato che interagisca con un datastore (o qualche altro backend) che tiene traccia delle informazioni di stato in modo trasparente in modo che il tuo client non debba farlo.

L'idea di un Object-Relational Mapper, in cui si importa un record di database come un oggetto e quindi lo si modifica, è applicabile e utile sia alla programmazione funzionale che alla programmazione orientata agli oggetti. L'unica avvertenza è che la programmazione funzionale non modifica l' oggetto in posizione, ma l'API del database può consentire di modificare il record in posizione. Il flusso di controllo del tuo cliente sarebbe simile a questo:

  • Importa il record come oggetto (l'API del database può bloccare il record a questo punto),
  • Leggi l'oggetto e il ramo in base al suo contenuto come preferisci,
  • Pacchetto un nuovo oggetto con le modifiche desiderate,
  • Passa il nuovo oggetto alla chiamata API appropriata che aggiorna il record sul database.

Il database aggiornerà il record con le modifiche. La programmazione funzionale pura potrebbe non consentire la riassegnazione di variabili nell'ambito del programma , ma l'API del database può comunque consentire aggiornamenti sul posto.


5

Mi trovo molto bene con Haskell. Il framework web Haskell più importante (paragonabile a Rails e Django) si chiama Yesod. Sembra avere un ORM multi-backend abbastanza interessante, indipendente dai tipi. Dai un'occhiata al capitolo Persistenza nel loro libro.


0

Database e programmazione funzionale possono essere fusi.

per esempio:

Clojure è un linguaggio di programmazione funzionale basato sulla teoria dei database relazionali.

               Clojure -> DBMS, Super Foxpro
                   STM -> TransactionMVCC
Persistent Collections -> db, table, col
              hash-map -> indexed data
                 Watch -> trigger, log
                  Spec -> constraint
              Core API -> SQL, Built-in function
              function -> Stored Procedure
             Meta Data -> System Table

Nota: nell'ultima specifica2, la specifica è più simile a RMDB. vedi: wiki spec-alpha2: Schema-and-select

Sostengo: la creazione di un modello di dati relazionali sulla mappa hash per ottenere una combinazione di vantaggi NoSQL e RMDB. Questa è in realtà un'implementazione inversa di posgtresql.

Digitazione anatra: se sembra un'anatra e ciarlatano come un'anatra, deve essere un'anatra.

Se il modello di dati di clojure come un RMDB, le strutture di clojure come un RMDB e la manipolazione dei dati di clojure come un RMDB, clojure deve essere un RMDB.

Clojure è un linguaggio di programmazione funzionale basato sulla teoria dei database relazionali

Tutto è RMDB

Implementa il modello di dati relazionali e la programmazione basata su hash-map (NoSQL)

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.