J'ai récemment appris la programmation fonctionnelle (en particulier Haskell, mais j'ai également suivi des tutoriels sur Lisp et Erlang). Bien que je trouve les concepts très éclairants, je ne vois toujours pas le côté pratique du concept «sans effets secondaires». Quels en sont les avantages pratiques? J'essaie de penser dans un état d'esprit fonctionnel, mais il y a des situations qui semblent trop complexes sans la possibilité de sauvegarder l'état de manière simple (je ne considère pas les monades de Haskell comme «faciles»).
Vaut-il la peine de continuer à apprendre en profondeur Haskell (ou un autre langage purement fonctionnel)? La programmation fonctionnelle ou sans état est-elle réellement plus productive que procédurale? Est-il probable que je continue à utiliser Haskell ou un autre langage fonctionnel plus tard, ou devrais-je l'apprendre uniquement pour la compréhension?
Je me soucie moins des performances que de la productivité. Je demande donc principalement si je serai plus productif dans un langage fonctionnel qu'un procédural / orienté objet / peu importe.
la source
Plus il y a de morceaux de votre programme sans état, plus il y a de façons de les assembler sans rien casser . La puissance du paradigme sans état ne réside pas dans l'apatridie (ou la pureté) en soi , mais dans la capacité qu'il vous donne à écrire des fonctions puissantes et réutilisables et à les combiner.
Vous pouvez trouver un bon tutoriel avec de nombreux exemples dans l'article de John Hughes Why Functional Programming Matters (PDF).
Vous serez beaucoup plus productif, surtout si vous choisissez un langage fonctionnel qui a également des types de données algébriques et des correspondances de motifs (Caml, SML, Haskell).
la source
La plupart des autres réponses se sont concentrées sur le côté performance (parallélisme) de la programmation fonctionnelle, ce que je pense est très important. Cependant, vous avez spécifiquement posé des questions sur la productivité, comme dans, pouvez-vous programmer la même chose plus rapidement dans un paradigme fonctionnel que dans un paradigme impératif.
Je trouve en fait (par expérience personnelle) que la programmation en F # correspond mieux à ma façon de penser, et donc c'est plus facile. Je pense que c'est la plus grande différence. J'ai programmé à la fois en F # et C #, et il y a beaucoup moins de "combat contre le langage" en F #, ce que j'adore. Vous n'avez pas à penser aux détails en F #. Voici quelques exemples de ce que j'ai trouvé que j'apprécie vraiment.
Par exemple, même si F # est typé statiquement (tous les types sont résolus au moment de la compilation), l'inférence de type détermine quels types vous avez, vous n'avez donc pas à le dire. Et s'il ne peut pas le comprendre, il rend automatiquement votre fonction / classe / quel que soit le générique. Vous n'avez donc jamais à écrire de générique, tout est automatique. Je trouve que cela signifie que je passe plus de temps à réfléchir au problème et moins à le mettre en œuvre. En fait, chaque fois que je reviens à C #, je trouve que cette inférence de type me manque vraiment, vous ne réalisez jamais à quel point c'est distrayant tant que vous n'avez plus besoin de le faire.
Aussi en F #, au lieu d'écrire des boucles, vous appelez des fonctions. C'est un changement subtil, mais significatif, car vous n'avez plus à penser à la construction de la boucle. Par exemple, voici un morceau de code qui passerait et correspondrait à quelque chose (je ne me souviens pas de quoi, c'est d'un puzzle de projet Euler):
Je me rends compte que faire un filtre puis une carte (c'est une conversion de chaque élément) en C # serait assez simple, mais il faut penser à un niveau inférieur. En particulier, vous devrez écrire la boucle elle-même et avoir votre propre instruction if explicite, et ce genre de choses. Depuis l'apprentissage de F #, je me suis rendu compte que je trouvais plus facile de coder de manière fonctionnelle, où si vous voulez filtrer, vous écrivez "filtre", et si vous voulez mapper, vous écrivez "carte", au lieu d'implémenter chacun des détails.
J'adore aussi l'opérateur |>, qui, je pense, sépare F # d'ocaml, et peut-être d'autres langages fonctionnels. C'est l'opérateur pipe, il vous permet de "canaliser" la sortie d'une expression dans l'entrée d'une autre expression. Cela fait que le code suit ce que je pense davantage. Comme dans l'extrait de code ci-dessus, c'est-à-dire "prenez la séquence de facteurs, filtrez-la, puis mappez-la". C'est un niveau de réflexion très élevé, que vous n'obtenez pas dans un langage de programmation impératif parce que vous êtes tellement occupé à écrire la boucle et les instructions if. C'est la chose qui me manque le plus chaque fois que je vais dans une autre langue.
Donc, en général, même si je peux programmer à la fois en C # et en F #, je trouve qu'il est plus facile d'utiliser F # parce que vous pouvez penser à un niveau supérieur. Je dirais que parce que les plus petits détails sont supprimés de la programmation fonctionnelle (au moins en F #), je suis plus productif.
Edit : J'ai vu dans l'un des commentaires que vous avez demandé un exemple d '"état" dans un langage de programmation fonctionnel. F # peut être écrit impérativement, voici donc un exemple direct de la façon dont vous pouvez avoir un état mutable en F #:
la source
a |> b (p1, p2)
c'est juste du sucre syntaxique pourb (a, p1, p2)
. Ajoutez à cela la bonne associativité et vous l'avez.Considérez tous les bogues difficiles que vous avez consacrés au débogage depuis longtemps.
Maintenant, combien de ces bogues étaient dus à des «interactions involontaires» entre deux composants séparés d'un programme? (Presque tous les bogues de thread ont cette forme: courses impliquant l'écriture de données partagées, blocages, ... De plus, il est courant de trouver des bibliothèques qui ont un effet inattendu sur l'état global, ou lire / écrire le registre / l'environnement, etc.) I postulerait qu'au moins 1 «bogue dur» sur 3 entre dans cette catégorie.
Maintenant, si vous passez à la programmation sans état / immuable / pure, tous ces bogues disparaissent. On vous présente de nouveaux défis au lieu (par exemple , lorsque vous ne voulez différents modules pour interagir avec l'environnement), mais dans un langage comme Haskell, ces interactions se réifiées explicitement dans le système de type, ce qui signifie que vous pouvez juste regarder le type de une fonction et une raison sur le type d'interactions qu'il peut avoir avec le reste du programme.
C'est la grande victoire de l'OMI «immuabilité». Dans un monde idéal, nous concevrions tous des API formidables et même lorsque les choses étaient modifiables, les effets seraient locaux et bien documentés et les interactions `` inattendues '' seraient réduites au minimum. Dans le monde réel, il existe de nombreuses API qui interagissent avec l'état global de multiples façons, et elles sont à l'origine des bogues les plus pernicieux. Aspirer à l'apatridie, c'est aspirer à se débarrasser des interactions involontaires / implicites / en coulisse entre les composants.
la source
Un avantage des fonctions sans état est qu'elles permettent le précalcul ou la mise en cache des valeurs de retour de la fonction. Même certains compilateurs C vous permettent de marquer explicitement les fonctions comme sans état pour améliorer leur optimisabilité. Comme beaucoup d'autres l'ont noté, les fonctions sans état sont beaucoup plus faciles à paralléliser.
Mais l'efficacité n'est pas la seule préoccupation. Une fonction pure est plus facile à tester et à déboguer puisque tout ce qui l'affecte est explicitement indiqué. Et lors de la programmation dans un langage fonctionnel, on prend l'habitude de rendre le moins de fonctions possible "sales" (avec E / S, etc.). Séparer les éléments avec état de cette manière est un bon moyen de concevoir des programmes, même dans des langages pas si fonctionnels.
Les langages fonctionnels peuvent prendre un certain temps à «obtenir», et il est difficile d'expliquer à quelqu'un qui n'a pas traversé ce processus. Mais la plupart des gens qui persistent assez longtemps réalisent finalement que le tapage en vaut la peine, même s'ils ne finissent pas par utiliser beaucoup les langages fonctionnels.
la source
sin(PI/3)
dans votre code, où PI est une constante, le compilateur pourrait évaluer cette fonction au moment de la compilation et incorporer le résultat dans le code généré.Sans état, il est très facile de paralléliser automatiquement votre code (comme les processeurs sont fabriqués avec de plus en plus de cœurs, c'est très important).
la source
Les applications Web sans état sont essentielles lorsque vous commencez à avoir un trafic plus élevé.
Il peut y avoir beaucoup de données utilisateur que vous ne souhaitez pas stocker côté client pour des raisons de sécurité par exemple. Dans ce cas, vous devez le stocker côté serveur. Vous pouvez utiliser la session par défaut des applications Web, mais si vous disposez de plusieurs instances de l'application, vous devrez vous assurer que chaque utilisateur est toujours dirigé vers la même instance.
Les équilibreurs de charge ont souvent la possibilité d'avoir des `` sessions persistantes '' où l'équilibreur de charge sait à quel serveur envoyer la demande des utilisateurs. Ce n'est pas l'idéal, par exemple, cela signifie qu'à chaque fois que vous redémarrez votre application Web, tous les utilisateurs connectés perdront leur session.
Une meilleure approche consiste à stocker la session derrière les serveurs Web dans une sorte de magasin de données, de nos jours, il existe de nombreux produits nosql disponibles pour cela (redis, mongo, elasticsearch, memcached). De cette façon, les serveurs Web sont sans état mais vous avez toujours un état côté serveur et la disponibilité de cet état peut être gérée en choisissant la bonne configuration de banque de données. Ces magasins de données ont généralement une grande redondance, il devrait donc presque toujours être possible d'apporter des modifications à votre application Web et même au magasin de données sans affecter les utilisateurs.
la source
J'ai écrit un article sur ce sujet il y a quelque temps: Sur l'importance de la pureté .
la source