Les bases de données et la programmation fonctionnelle sont-elles en désaccord?

122

Je suis développeur Web depuis un certain temps maintenant et j'ai récemment commencé à apprendre la programmation fonctionnelle. Comme d'autres, j'ai eu du mal à appliquer bon nombre de ces concepts à mon travail professionnel. Pour moi, la raison principale en est que je vois un conflit entre l'objectif de FP de rester apatride semble assez en contradiction avec le fait que la plupart des travaux de développement Web que j'ai effectués ont été fortement liés aux bases de données, qui sont très centrées sur les données.

Une chose qui a fait de moi un développeur beaucoup plus productif du côté de la POO a été la découverte de mappeurs relationnels objet comme MyGeneration d00dads pour .Net, Class :: DBI pour perl, ActiveRecord pour ruby, etc. Cela m'a permis de rester à l'écart de l'écriture, insérer et sélectionner des déclarations toute la journée, et se concentrer sur le travail avec les données facilement en tant qu'objets. Bien sûr, je pouvais toujours écrire des requêtes SQL lorsque leur puissance était nécessaire, mais sinon, c'était bien résumé dans les coulisses.

Maintenant, en ce qui concerne la programmation fonctionnelle, il semble que de nombreux frameworks Web FP tels que Links nécessitent l'écriture de beaucoup de code SQL standard, comme dans cet exemple . Weblocks semble un peu mieux, mais il semble utiliser une sorte de modèle POO pour travailler avec des données, et nécessite toujours que le code soit écrit manuellement pour chaque table de votre base de données comme dans cet exemple . Je suppose que vous utilisez une génération de code pour écrire ces fonctions de mappage, mais cela semble décidément non lisp.

(Notez que je n'ai pas regardé de très près Weblocks ou Links, je ne comprends peut-être pas comment ils sont utilisés).

La question est donc, pour les parties d'accès à la base de données (qui, à mon avis, sont assez volumineuses) d'une application Web, ou d'un autre développement nécessitant une interface avec une base de données SQL, nous semblons être obligés de suivre l'un des chemins suivants:

  1. N'utilisez pas de programmation fonctionnelle
  2. Accédez aux données d'une manière ennuyeuse et non abstraite qui implique l'écriture manuelle de beaucoup de code SQL ou de type SQL comme des liens
  3. Forcer notre langage fonctionnel dans un paradigme pseudo-POO, supprimant ainsi une partie de l'élégance et de la stabilité d'une véritable programmation fonctionnelle.

De toute évidence, aucune de ces options ne semble idéale. A trouvé un moyen de contourner ces problèmes? Y a-t-il vraiment un problème ici?

Remarque: Personnellement, je suis plus familier avec LISP sur le front FP, donc si vous voulez donner des exemples et connaître plusieurs langues FP, lisp serait probablement la langue préférée de choix

PS: Pour les problèmes spécifiques à d'autres aspects du développement Web, consultez cette question .

Tristan Havelick
la source
4
Découvrez ClojureQL et HaskellDB. Ce sont des couches d'abstraction utilisant l'algèbre relationnelle.
Masse
10
Vous partez avec la mauvaise prémisse. La programmation fonctionnelle consiste à gérer explicitement et correctement l'état. Ils fonctionnent très bien avec les bases de données, en fait.
Lucian
3
SQL est l'un des langages axés sur la programmation fonctionnelle les plus réussis, je ne pense pas qu'il y ait de difficulté inhérente.
Douglas
3
Vos # 2 et # 3 sont une fausse dichotomie. L'écriture de SQL brut n'est pas quelque chose qui doit nécessairement être évité, et les abstractions sur une base de données ne doivent pas nécessairement être de type POO.
Dan Burton

Réponses:

45

Tout d'abord, je ne dirais pas que CLOS (Common Lisp Object System) est "pseudo-OO". C'est OO de première classe.

Deuxièmement, je crois que vous devriez utiliser le paradigme qui correspond à vos besoins.

Vous ne pouvez pas stocker de données sans état, alors qu'une fonction est un flux de données et n'a pas vraiment besoin d'état.

Si vous avez plusieurs besoins mélangés, mélangez vos paradigmes. Ne vous limitez pas à n'utiliser que le coin inférieur droit de votre boîte à outils.

Svante
la source
3
juste pour le plaisir, j'ai pensé mentionner le datalog qui tente d'être une base de données plus sans état. il enregistre toutes les actions, telles que «l'utilisateur aime la publication 1233». Ces actions résolvent le véritable état de la base de données. La clé est que les requêtes ne sont que des faits plutôt que des mutations ...
Chet
80

En venant à ceci du point de vue d'une personne de base de données, je trouve que les développeurs frontaux essaient trop dur de trouver des moyens d'adapter les bases de données à leur modèle plutôt que d'envisager les moyens les plus efficaces d'utiliser une base de données qui ne sont pas orientées objet ou fonctionnelles mais relationnelles et utilisant théorie des ensembles. J'ai vu que cela aboutissait généralement à un code peu performant. Et en outre, cela crée un code difficile à régler en termes de performances.

Lors de l'examen de l'accès à la base de données, il y a trois considérations principales: l'intégrité des données (pourquoi toutes les règles métier doivent être appliquées au niveau de la base de données et non via l'interface utilisateur), les performances et la sécurité. SQL est écrit pour gérer les deux premières considérations plus efficacement que n'importe quel langage frontal. Parce qu'il est spécifiquement conçu pour cela. La tâche d'une base de données est très différente de la tâche d'une interface utilisateur. Faut-il s'étonner que le type de code le plus efficace pour gérer la tâche soit conceptuellement différent?

Et les bases de données contiennent des informations essentielles à la survie d'une entreprise. Il n'est pas étonnant que les entreprises ne soient pas disposées à expérimenter de nouvelles méthodes lorsque leur survie est en jeu. Heck de nombreuses entreprises ne sont même pas disposées à passer à de nouvelles versions de leur base de données existante. Il existe donc un conservatisme inhérent à la conception de bases de données. Et c'est délibérément ainsi.

Je n'essaierais pas d'écrire T-SQL ou d'utiliser des concepts de conception de base de données pour créer votre interface utilisateur, pourquoi essayez-vous d'utiliser votre langage d'interface et vos concepts de conception pour accéder à ma base de données? Parce que vous pensez que SQL n'est pas assez sophistiqué (ou nouveau)? Ou vous ne vous sentez pas à l'aise avec ça? Ce n'est pas parce que quelque chose ne correspond pas au modèle avec lequel vous vous sentez le plus à l'aise que c'est mauvais ou faux. Cela signifie qu'il est différent et probablement différent pour une raison légitime. Vous utilisez un outil différent pour une tâche différente.

HLGEM
la source
"SQL est écrit pour gérer les deux premières considérations plus efficacement que n'importe quel langage frontal." Oh vraiment? Pourquoi, alors, est-ce que les contraintes SQL ne peuvent toujours pas faire des choses comme ça ?
Robin Green
Mais un déclencheur peut, c'est l'un des principaux objectifs des déclencheurs pour pouvoir gérer des contraintes complexes.
HLGEM
2
Je propose de déplacer votre dernier paragraphe pour qu'il soit le premier paragraphe. Très bon point, qui fait écho à ce que d'autres préconisent également, qui est une approche multi-paradigme (pas une taille unique).
pestophage du
31

Vous devriez regarder l'article «Out of the Tar Pit» de Ben Moseley et Peter Marks, disponible ici: «Out of the Tar Pit» (6 février 2006)

C'est un classique moderne qui détaille un paradigme / système de programmation appelé Programmation fonctionnelle-relationnelle. Bien que n'étant pas directement lié aux bases de données, il explique comment isoler les interactions avec le monde extérieur (bases de données, par exemple) du noyau fonctionnel d'un système.

Le papier discute également comment implémenter un système où l'état interne de l'application est défini et modifié en utilisant une algèbre relationnelle, qui est évidemment liée aux bases de données relationnelles.

Cet article ne donnera pas une réponse exacte sur la façon d'intégrer les bases de données et la programmation fonctionnelle, mais il vous aidera à concevoir un système pour minimiser le problème.

Kevin Albrecht
la source
4
Quel beau papier. Le lien que vous donnez est cassé (temporairement?), Mais j'ai trouvé le papier aussi à shaffner.us/cs/papers/tarpit.pdf
pestophagous
2
@queque Le lien d'origine est toujours mort. J'ai mis le nouveau lien dans la réponse de Kevin.
David Tonhofer
25
  1. Les langages fonctionnels n'ont pas pour but de rester apatrides, ils ont pour but de rendre explicite la gestion de l'état. Par exemple, dans Haskell, vous pouvez considérer la monade d'état comme le cœur de l'état «normal» et la monade IO une représentation d'état qui doit exister en dehors du programme. Ces deux monades vous permettent (a) de représenter explicitement des actions avec état et (b) de créer des actions avec état en les composant à l'aide d'outils référentiellement transparents.

  2. Vous référencez un certain nombre d'ORM qui, par leur nom, résument les bases de données sous forme d'ensembles d'objets. Ce n'est vraiment pas ce que représentent les informations d'une base de données relationnelle! Par son nom, il représente des données relationnelles. SQL est une algèbre (langage) pour gérer les relations sur un ensemble de données relationnelles et est en fait assez "fonctionnel" lui-même. J'évoque cela afin de considérer que (a) les ORM ne sont pas le seul moyen de mapper les informations de base de données, (b) que SQL est en fait un langage assez sympa pour certaines conceptions de bases de données, et (c) que les langages fonctionnels ont souvent une algèbre relationnelle des mappages qui exposent la puissance de SQL de manière idiomatique (et dans le cas de Haskell, avec une vérification de type).

Je dirais que la plupart des lisps sont le langage fonctionnel d'un pauvre. Il est parfaitement capable d'être utilisé selon les pratiques fonctionnelles modernes, mais comme il ne les nécessite pas, la communauté est moins susceptible de les utiliser. Cela conduit à un mélange de méthodes qui peuvent être très utiles mais qui obscurcit certainement la façon dont les interfaces fonctionnelles pures peuvent toujours utiliser les bases de données de manière significative.

J. Abrahamson
la source
15

Je ne pense pas que la nature sans état des langages fp soit un problème de connexion aux bases de données. Lisp est un langage de programmation fonctionnel non pur, il ne devrait donc pas avoir de problème avec l'état. Et les langages de programmation fonctionnels purs comme Haskell ont des moyens de gérer les entrées et les sorties qui peuvent être appliqués à l'utilisation de bases de données.

D'après votre question, il semble que votre principal problème réside dans la recherche d'un bon moyen d'abstraire les données basées sur les enregistrements que vous récupérez de votre base de données dans quelque chose qui est lisp-y (lisp-ish?) Sans avoir à écrire beaucoup de SQL code. Cela ressemble plus à un problème avec les outils / bibliothèques qu'à un problème avec le paradigme du langage. Si vous voulez faire de la FP pure, peut-être que lisp n'est pas le bon langage pour vous. Common lisp semble plus sur l'intégration de bonnes idées de oo, fp et d'autres paradigmes que sur pure fp. Peut-être devriez-vous utiliser Erlang ou Haskell si vous voulez emprunter la voie FP pure.

Je pense que les idées «pseudo-oo» de lisp ont aussi leur mérite. Vous voudrez peut-être les essayer. S'ils ne correspondent pas à la manière dont vous souhaitez travailler avec vos données, vous pouvez essayer de créer une couche au-dessus de Weblocks qui vous permet de travailler avec vos données comme vous le souhaitez. Cela pourrait être plus facile que de tout écrire vous-même.

Avertissement: je ne suis pas un expert Lisp. Je m'intéresse principalement aux langages de programmation et j'ai joué avec Lisp / CLOS, Scheme, Erlang, Python et un peu de Ruby. Dans la vie de programmation quotidienne, je suis toujours obligé d'utiliser C #.

Mendelt
la source
3
Erlang n'est pas du pur FP selon aucune définition du mot. Vous écrivez erlang en créant de nombreux processus (tous fonctionnant en parallèle) qui s'envoient des messages, un peu comme des objets dans, par exemple, smalltalk. Donc, d'un point de vue de haut niveau, cela peut même sembler quelque peu OO-ish, et a définitivement un état. Si vous effectuez un zoom avant (dans le code exécuté dans un processus), il semble plus fonctionnel, mais n'est toujours pas purement fonctionnel, car vous pouvez envoyer des messages (et donc faire des E / S, etc.) de n'importe où, ainsi que stocker " variables globales "(globales au processus, à l'intérieur de quelque chose appelé" processus dict ".)
Amadiro
@Amadiro "a définitivement un état". Bien sûr que oui. Nous avons toujours un état. Le problème n'est pas l'état, c'est l' état mutable . Une bonne partie de la "programmation fonctionnelle" consiste à se débarrasser des représentations d'état qui sont fragiles (par exemple, les instances d'objet qui sont modifiées par d'autres processus pendant que nous y faisons référence, d'une manière non transactionnelle pour ajouter l'insulte à la blessure). Erlang a un état non mutable et atteint donc ce critère de programmation fonctionnelle. Et donc: les bases de données de toute nature ne sont jamais un problème. Les mises à jour de la base de données sont un problème (voir aussi l' affirmation désagréable dans Prolog).
David Tonhofer
15

Si votre base de données ne détruit pas les informations, alors vous pouvez travailler avec elle d'une manière fonctionnelle cohérente avec des valeurs de programmation "purement fonctionnelles" en travaillant dans les fonctions de la base de données entière comme valeur.

Si au temps T la base de données indique que "Bob aime Suzie", et que vous aviez une fonction likes qui acceptait une base de données et un liker, alors tant que vous pouvez récupérer la base de données au temps T vous avez un programme fonctionnel pur qui implique une base de données . par exemple

# 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"

Pour ce faire, vous ne pouvez jamais jeter les informations que vous pourriez utiliser (ce qui signifie en pratique que vous ne pouvez pas jeter les informations), de sorte que vos besoins de stockage augmenteront de manière monotone. Mais vous pouvez commencer à travailler avec votre base de données comme une série linéaire de valeurs discrètes, où les valeurs suivantes sont liées aux précédentes par le biais de transactions.

C'est par exemple l' idée majeure de Datomic .

animal
la source
Agréable. Je ne connaissais même pas Datomic. Voir aussi: justification de Datomic .
David Tonhofer
12

Pas du tout. Il existe un genre de bases de données connues sous le nom de «bases de données fonctionnelles», dont Mnesia est peut-être l'exemple le plus accessible. Le principe de base est que la programmation fonctionnelle est déclarative, donc elle peut être optimisée. Vous pouvez implémenter une jointure à l'aide de List Comprehensions sur des collections persistantes et l'optimiseur de requêtes peut déterminer automatiquement comment implémenter l'accès au disque.

Mnesia est écrit en Erlang et il existe au moins un framework web ( Erlyweb ) disponible pour cette plateforme. Erlang est intrinsèquement parallèle à un modèle de threading sans partage, donc à certains égards, il se prête à des architectures évolutives.

ConcernedOfTunbridgeWells
la source
1
Je ne pense pas que ce soit vraiment une solution. Il existe également des bases de données orientées objet, mais vous souhaitez généralement vous connecter à une base de données SQL relationnelle ancienne.
jalf
4
Vous avez toujours une incompatibilité d'impédance avec les langages OO et les bases de données de la même manière que vous avez une incompatibilité d'impédance SQL fonctionnelle.
ConcernedOfTunbridgeWells
1
@ConcernedOfTunbridgeWells Je dirai arbitrairement que cette discordance d'impédance est le fruit de l'imagination des gens avec des marteaux qui ont besoin que tout soit des clous. Des couches très fines et des connaissances sur SQL peuvent élégamment aller loin, d'où jOOQ et des shims similaires.
David Tonhofer
6

Une base de données est le moyen idéal pour suivre l'état d'une API sans état. Si vous vous abonnez à REST, votre objectif est d'écrire du code sans état qui interagit avec une banque de données (ou un autre backend) qui garde une trace des informations d'état de manière transparente afin que votre client n'ait pas à le faire.

L'idée d'un mappeur objet-relationnel, dans lequel vous importez un enregistrement de base de données en tant qu'objet puis le modifiez, est tout aussi applicable et utile à la programmation fonctionnelle qu'à la programmation orientée objet. La seule mise en garde est que la programmation fonctionnelle ne modifie pas l' objet en place, mais l'API de base de données peut vous permettre de modifier l' enregistrement en place. Le flux de contrôle de votre client ressemblerait à ceci:

  • Importez l'enregistrement en tant qu'objet (l'API de base de données peut verrouiller l'enregistrement à ce stade),
  • Lisez l'objet et la branche en fonction de son contenu comme vous le souhaitez,
  • Emballez un nouvel objet avec les modifications souhaitées,
  • Transmettez le nouvel objet à l'appel d'API approprié qui met à jour l'enregistrement dans la base de données.

La base de données mettra à jour l'enregistrement avec vos modifications. La programmation fonctionnelle pure peut interdire la réaffectation des variables dans le cadre de votre programme , mais votre API de base de données peut toujours autoriser les mises à jour sur place.

Brice frit
la source
5

Je suis plus à l'aise avec Haskell. Le framework Web Haskell le plus important (comparable à Rails et Django) est appelé Yesod. Il semble avoir un ORM multi-backend assez cool, sécurisé. Jetez un œil au chapitre Persistance dans leur livre.

Honza Pokorny
la source
0

Les bases de données et la programmation fonctionnelle peuvent être fusionnées.

par exemple:

Clojure est un langage de programmation fonctionnel basé sur la théorie des bases de données relationnelles.

               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

Remarque: dans la dernière spécification2, la spécification ressemble plus à RMDB. voir: wiki spec-alpha2: Schema-and-select

Je préconise: Construire un modèle de données relationnelles au-dessus de la carte de hachage pour obtenir une combinaison des avantages NoSQL et RMDB. Il s'agit en fait d'une implémentation inversée de posgtresql.

Duck Typing: Si cela ressemble à un canard et charlatan comme un canard, ce doit être un canard.

Si le modèle de données de clojure comme une RMDB, les installations de clojure comme une RMDB et la manipulation des données de clojure comme une RMDB, clojure doit être une RMDB.

Clojure est un langage de programmation fonctionnel basé sur la théorie des bases de données relationnelles

Tout est RMDB

Mettre en œuvre un modèle de données relationnel et une programmation basée sur hash-map (NoSQL)

Lin Pengcheng
la source