Le génie logiciel tel qu'il est enseigné aujourd'hui est entièrement axé sur la programmation orientée objet et la vision orientée objet "naturelle" du monde. Il existe une méthodologie détaillée qui décrit comment transformer un modèle de domaine en un modèle de classe avec plusieurs étapes et de nombreux artefacts (UML) comme des diagrammes de cas d'utilisation ou des diagrammes de classe. De nombreux programmeurs ont internalisé cette approche et ont une bonne idée de la façon de concevoir une application orientée objet à partir de zéro.
Le nouveau battage médiatique est la programmation fonctionnelle, qui est enseignée dans de nombreux livres et tutoriels. Mais qu'en est-il de l'ingénierie logicielle fonctionnelle? En lisant sur LISP et Clojure, je suis arrivé à deux déclarations intéressantes:
Les programmes fonctionnels sont souvent développés de bas en haut plutôt que de haut en bas ("On LISP", Paul Graham)
Les programmeurs fonctionnels utilisent des cartes où les programmeurs OO utilisent des objets/classes ("Clojure for Java Programmers", talk by Rich Hickley).
Quelle est donc la méthodologie pour une conception systématique (basée sur un modèle?) D'une application fonctionnelle, c'est-à-dire en LISP ou en Clojure? Quelles sont les étapes courantes, quels artefacts dois-je utiliser, comment les mapper de l'espace du problème à l'espace de la solution?
Dieu merci, les ingénieurs en logiciel n'ont pas encore découvert la programmation fonctionnelle. Voici quelques parallèles:
De nombreux OO "motifs de conception" sont capturés comme des fonctions d'ordre supérieur. Par exemple, le motif Visiteur est connu dans le monde fonctionnel comme un "pli" (ou si vous êtes un théoricien pointu) , un "catamorphisme"). Dans les langages fonctionnels, les types de données sont principalement des arbres ou des tuples, et chaque type d'arbre a un catamorphisme naturel qui lui est associé.
Ces fonctions d'ordre supérieur s'accompagnent souvent de certaines lois de programmation, appelées "théorèmes libres".
Les programmeurs fonctionnels utilisent beaucoup moins les diagrammes que les OO programmeurs. Une grande partie de ce qui est exprimé en OO les diagrammes sont plutôt exprimés en , ou dans des "signatures", que vous devriez considérer comme des "types de modules". Haskell a également des "classes de types", qui sont un peu comme un type d'interface.
Ces programmeurs fonctionnels qui utilisent des types pensent généralement que "une fois que vous avez trouvé les bons types, le code s’écrit pratiquement lui-même".
Tous les langages fonctionnels n'utilisent pas de types explicites, mais le livre How To Design Programs , un excellent livre pour apprendre Scheme/LISP/Clojure, s'appuie fortement sur des "descriptions de données", qui sont étroitement liées aux types.
Quelle est donc la méthodologie pour une conception systématique (basée sur un modèle?) D'une application fonctionnelle, c'est-à-dire en LISP ou en Clojure?
Toute méthode de conception basée sur l'abstraction des données fonctionne bien. Je pense que c'est plus facile lorsque le langage a des types explicites, mais cela fonctionne même sans. Un bon livre sur les méthodes de conception pour les types de données abstraits, qui est facilement adapté à la programmation fonctionnelle, est Abstraction and Specification in Program Development par Barbara Liskov et John Guttag , la première édition . Liskov a remporté le prix Turing en partie pour ce travail.
Une autre méthodologie de conception unique à LISP consiste à décider quelles extensions de langue seraient utiles dans le domaine problématique dans lequel vous travaillez, puis à utiliser des macros hygiéniques pour ajouter ces constructions à votre langue. Un bon endroit pour lire sur ce type de conception est l'article de Matthew Flatt Création de langues dans une raquette . L'article peut se trouver derrière un mur payant. Vous pouvez également trouver des informations plus générales sur ce type de conception en recherchant le terme "langage intégré spécifique au domaine"; pour des conseils et des exemples particuliers au-delà de ce que couvre Matthew Flatt, je commencerais probablement par Graham Sur LISP ou peut-être LISI commun ANSI .
Quelles sont les étapes courantes, quels artefacts dois-je utiliser?
Étapes courantes:
Identifiez les données de votre programme et les opérations qu'il contient, et définissez un type de données abstrait représentant ces données.
Identifiez les actions ou les schémas de calcul courants et exprimez-les sous forme de fonctions ou de macros d'ordre supérieur. Attendez-vous à prendre cette étape dans le cadre de la refactorisation.
Si vous utilisez un langage fonctionnel tapé, utilisez le vérificateur de type tôt et souvent. Si vous utilisez LISP ou Clojure, la meilleure pratique consiste à écrire d'abord les contrats de fonction, y compris les tests unitaires - c'est un développement piloté par les tests au maximum. Et vous voudrez utiliser n'importe quelle version de QuickCheck qui a été portée sur votre plate-forme, qui dans votre cas ressemble à ce qu'elle s'appelle ClojureCheck . C'est une bibliothèque extrêmement puissante pour construire des tests aléatoires de code qui utilise des fonctions d'ordre supérieur.
Pour Clojure, je recommande de revenir à une bonne vieille modélisation relationnelle. Out of the Tarpit est une lecture inspirante.
Personnellement, je trouve que toutes les bonnes pratiques habituelles du développement OO s'appliquent également à la programmation fonctionnelle - juste avec quelques ajustements mineurs pour tenir compte de la vision du monde fonctionnelle. Du point de vue méthodologique, vous ne t vraiment besoin de faire quelque chose de fondamentalement différent.
Mon expérience vient du fait que je suis passé de Java à Clojure ces dernières années.
Quelques exemples:
Comprendre votre domaine d'activité/modèle de données - tout aussi important si vous allez concevoir un modèle d'objet ou créer une structure de données fonctionnelle avec des cartes imbriquées. À certains égards, FP peut être plus facile car il vous encourage à penser le modèle de données séparément des fonctions/processus, mais vous devez toujours faire les deux.
Orientation du service dans la conception - fonctionne en fait très bien du point de vue FP, car un service typique est vraiment juste une fonction avec quelques effets secondaires. Je pense que le "bas up "vue du développement de logiciels parfois adopté dans le monde LISP est en fait juste de bons principes de conception d'API orientés services sous un autre aspect.
Test Driven Development - fonctionne bien dans FP langages, en fait parfois même mieux parce que les fonctions pures se prêtent extrêmement bien à l'écriture de tests clairs et reproductibles sans avoir besoin de configuration un environnement dynamique. Vous pouvez également créer des tests séparés pour vérifier l'intégrité des données (par exemple, cette carte contient-elle toutes les clés que j'attends, pour équilibrer le fait que dans une langue OO la la définition de classe appliquerait ceci pour vous au moment de la compilation).
Prototying/itération - fonctionne aussi bien avec FP. Vous pourriez même être en mesure de créer des prototypes en direct avec des utilisateurs si vous êtes très doué pour créer des outils/DSL et les utiliser au REPL.
La programmation OO couple étroitement les données avec le comportement. La programmation fonctionnelle sépare les deux. Vous n'avez donc pas de diagrammes de classes, mais vous avez des structures de données, et vous avez en particulier des types de données algébriques. Ces types peuvent être écrits pour correspondre très étroitement à votre domaine, notamment en éliminant les valeurs impossibles par construction.
Il n'y a donc pas de livres et de livres à ce sujet, mais il existe une approche bien établie pour, comme dit le proverbe, rendre les valeurs impossibles non représentables.
Ce faisant, vous pouvez faire une gamme de choix pour représenter à la place certains types de données en tant que fonctions, et inversement, représenter plutôt certaines fonctions comme une union de types de données afin que vous puissiez obtenir, par exemple, la sérialisation, des spécifications plus strictes, l'optimisation, etc. .
Ensuite, étant donné que vous écrivez des fonctions sur vos annonces de telle sorte que vous établissez une sorte de algèbre - c'est-à-dire qu'il existe des lois fixes qui s'appliquent à ces fonctions. Certains sont peut-être idempotents - les mêmes après plusieurs applications. Certains sont associatifs. Certains sont transitifs, etc.
Vous avez maintenant un domaine sur lequel vous avez des fonctions qui composent selon des lois bien comportées. Une simple DSL embarquée!
Oh, et compte tenu des propriétés, vous pouvez bien sûr en écrire des tests aléatoires automatisés (ala QuickCheck) .. et ce n'est que le début.
La conception orientée objet n'est pas la même chose que l'ingénierie logicielle. L'ingénierie logicielle concerne tout le processus de passage des exigences à un système opérationnel, dans les délais et avec un faible taux de défauts. La programmation fonctionnelle peut être différente de l'OO, mais elle ne supprime pas les exigences, les conceptions détaillées et de haut niveau, la vérification et les tests, les métriques logicielles, l'estimation, et tout ce "truc d'ingénierie logicielle".
De plus, les programmes fonctionnels présentent une modularité et d'autres structures. Vos conceptions détaillées doivent être exprimées en termes de concepts dans cette structure.
Voir ma réponse à un autre post:
Comment Clojure aborde-t-il la séparation des préoccupations?
Je conviens que davantage de choses doivent être écrites sur le sujet sur la façon de structurer les grandes applications qui utilisent une approche FP (De plus, il faut en faire plus pour documenter les interfaces utilisateur pilotées par FP))
Une approche consiste à créer un DSL interne dans le langage de programmation fonctionnel de choix. Le "modèle" est alors un ensemble de règles commerciales exprimées dans la LIS.
Bien que cela puisse être considéré comme naïf et simpliste, je pense que les "recettes de conception" (une approche systématique de la résolution de problèmes appliquée à la programmation comme le préconisent Felleisen et al. Dans leur livre HtDP ) seraient proches de ce que vous semblent chercher.
Ici, quelques liens:
http://www.northeastern.edu/magazine/0301/programming.html
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.86.8371
J'ai récemment trouvé ce livre: Modélisation fonctionnelle et réactive des domaines
Je pense que cela correspond parfaitement à votre question.
De la description du livre:
La modélisation fonctionnelle et réactive de domaine vous apprend à penser le modèle de domaine en termes de fonctions pures et à les composer pour construire de plus grandes abstractions. Vous commencerez par les bases de la programmation fonctionnelle et progresserez progressivement vers les concepts et modèles avancés que vous devez connaître pour implémenter des modèles de domaine complexes. Le livre montre à quel point les modèles FP FP comme les types de données algébriques, la conception basée sur les classes de caractères et l'isolement des effets secondaires peuvent faire en sorte que votre modèle se compose pour la lisibilité et la vérifiabilité.
J'ai trouvé que le développement piloté par le comportement était un choix naturel pour développer rapidement du code à la fois dans Clojure et SBCL. Le vrai avantage de tirer parti de BDD avec un langage fonctionnel est que j'ai tendance à écrire des tests unitaires à grain beaucoup plus fin que d'habitude lorsque j'utilise des langages procéduraux parce que je réussis beaucoup mieux à décomposer le problème en plus petits morceaux de fonctionnalité.
Il y a le style "calcul de programme"/"conception par calcul" associé au professeur Richard Bird et au groupe d'algèbre de programmation de l'Université d'Oxford (Royaume-Uni), je ne pense pas qu'il soit trop exagéré de considérer cela comme une méthodologie.
Personnellement, bien que j'aime le travail produit par le groupe AoP, je n'ai pas la discipline pour pratiquer le design de cette manière moi-même. Mais c'est ma lacune, et non celle du calcul du programme.
Honnêtement, si vous voulez des recettes de conception pour des programmes fonctionnels, jetez un œil aux bibliothèques de fonctions standard telles que le Prélude de Haskell. Dans FP, les modèles sont généralement capturés par des procédures d'ordre supérieur (fonctions qui opèrent sur des fonctions) elles-mêmes. Donc, si un motif est vu, souvent une fonction d'ordre supérieur est simplement créée pour capturer ce motif.
Un bon exemple est fmap. Cette fonction prend une fonction comme argument et l'applique à tous les "éléments" du deuxième argument. Puisqu'il fait partie de la classe de type Functor, toute instance d'un Functor (comme une liste, un graphique, etc ...) peut être passée comme deuxième argument à cette fonction. Il capture le comportement général de l'application d'une fonction à chaque élément de son deuxième argument.