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», exposé de 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?
Réponses:
Dieu merci, les ingénieurs en logiciel n'ont pas encore découvert la programmation fonctionnelle. Voici quelques parallèles:
De nombreux "modèles de conception" OO sont capturés en tant que fonctions d'ordre supérieur. Par exemple, le modèle Visitor 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 de diagrammes que les programmeurs OO. Une grande partie de ce qui est exprimé dans les diagrammes OO est plutôt exprimée en types ou en «signatures», que vous devriez considérer comme des «types de modules». Haskell a également des "classes de type", 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.
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 s'adapte facilement à 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 langage seraient utiles dans le domaine problématique dans lequel vous travaillez, puis à utiliser des macros hygiéniques pour ajouter ces constructions à votre langage. 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's On Lisp ou peut-être ANSI Common Lisp .
É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'on 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.
la source
Pour Clojure, je recommande de revenir à une bonne vieille modélisation relationnelle. Out of the Tarpit est une lecture inspirante.
la source
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 n'avez pas vraiment besoin de faire quelque chose de fondamentalement différent.
Mon expérience vient de mon passage 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 envisagez de concevoir un modèle d'objet ou de créer une structure de données fonctionnelle avec des cartes imbriquées. À certains égards, la PF peut être plus facile car elle 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 de la PF, car un service typique n'est vraiment qu'une fonction avec quelques effets secondaires. Je pense que la vision «ascendante» du développement de logiciels parfois adoptée dans le monde Lisp n'est en fait que de bons principes de conception d'API orientés services sous un autre aspect.
Test Driven Development - fonctionne bien dans les langages FP, en fait parfois même mieux parce que les fonctions pures se prêtent très bien à l'écriture de tests clairs et reproductibles sans avoir besoin de mettre en place un environnement dynamique. Vous pouvez également vouloir 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 un langage OO, la définition de classe appliquerait cela pour vous au moment de la compilation).
Prototying / itération - fonctionne tout 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 source
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é cela, vous écrivez des fonctions sur vos annonces de telle sorte que vous établissez une sorte d' 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 source
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.
la source
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.
la source
Voir ma réponse à un autre post:
Comment Clojure aborde-t-il la séparation des préoccupations?
Je suis d'accord qu'il faut rédiger davantage 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)
la source
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 semblez rechercher.
Ici, quelques liens:
http://www.northeastern.edu/magazine/0301/programming.html
http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.86.8371
la source
J'ai récemment trouvé ce livre: Modélisation de domaine fonctionnelle et réactive
Je pense que cela correspond parfaitement à votre question.
De la description du livre:
la source
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.
la source
J'ai trouvé que le développement piloté par le comportement convenait naturellement au développement rapide de code dans Clojure et SBCL. Le véritable 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, car je réussis beaucoup mieux à décomposer le problème en blocs de fonctionnalités plus petits.
la source
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.
la source
Bien,
Généralement, de nombreux langages de programmation fonctionnelle sont utilisés dans les universités pendant longtemps pour les "problèmes de petits jouets".
Ils deviennent de plus en plus populaires maintenant, car la POO a des problèmes avec la "programmation parallèle" en raison de "l'état". Et parfois, le style fonctionnel est meilleur pour les problèmes actuels comme Google MapReduce.
Je suis sûr que, lorsque des gars functioanl frapperont le mur [essayez d'implémenter des systèmes de plus de 1.000.000 lignes de code], certains d'entre eux viendront avec de nouvelles méthodologies d'ingénierie logicielle avec des mots à la mode :-). Ils devraient répondre à la vieille question: comment diviser le système en morceaux afin que nous puissions «mordre» chaque morceau un par un? [travail itératif, inceremental et évolutif] utilisant le style fonctionnel.
Mais les programmes fonctionnels seront-ils utilisés pour de si gros systèmes? Deviendront-ils le courant principal? Telle est la question .
Et personne ne peut venir avec une méthodologie réaliste sans implémenter de si gros systèmes, se salir les mains. Vous devez d'abord vous salir les mains, puis suggérer une solution. Solutions-Suggestions sans "vraies douleurs et saletés" seront "fantastiques".
la source