Question

J'ai récemment étudié 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 & "Pas d’effets secondaires &"; concept. 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 sauver facilement l'état (je ne considère pas les monades de Haskell 'faciles').

Est-il utile de continuer à apprendre Haskell (ou un autre langage purement fonctionnel) en profondeur? La programmation fonctionnelle ou sans état est-elle plus productive que la procédure? Est-il probable que je continuerai à utiliser Haskell ou un autre langage fonctionnel ultérieurement, ou devrais-je l'apprendre uniquement pour la compréhension?

Je me soucie moins de la performance que de la productivité. Je demande donc principalement si je serai plus productif dans un langage fonctionnel que dans un langage procédural / orienté objet / peu importe.

Était-ce utile?

La solution

Lisez la Programmation fonctionnelle en un mot .

La programmation sans état présente de nombreux avantages, notamment le code radicalement multithread et simultané. En clair, l'état mutable est l'ennemi du code multithread. Si les valeurs sont immuables par défaut, les programmeurs n'ont pas à craindre qu'un seul thread ne mue la valeur d'état partagé entre deux threads, ce qui élimine toute une classe de bogues multithreading liés à des conditions de concurrence. En l'absence de conditions de concurrence, il n'y a aucune raison d'utiliser des verrous. L'immuabilité élimine également une autre catégorie de bogues liés aux blocages.

C'est la raison principale pour laquelle la programmation fonctionnelle est importante et probablement la meilleure pour sauter dans le train de programmation fonctionnelle. Il existe également de nombreux autres avantages, notamment un débogage simplifié (les fonctions sont pures et ne mutent pas dans d'autres parties d'une application), un code plus concis et plus expressif, moins de code passe-partout par rapport aux langages qui dépendent fortement des modèles de conception, et le compilateur peut optimiser votre code de manière plus agressive.

Autres conseils

Plus votre programme contient d’éléments sans état, plus vous disposez de moyens de les assembler sans faille . La puissance du paradigme sans état ne réside pas dans l'état sans état (ou la pureté) en soi , mais dans sa capacité à écrire de puissantes fonctions réutilisables et à les combiner.

Vous pouvez trouver un bon tutoriel avec beaucoup d'exemples dans l'article de John Hughes Pourquoi la programmation fonctionnelle est importante (PDF).

Vous serez gobs plus productif, en particulier si vous choisissez un langage fonctionnel qui comporte également des types de données algébriques et une correspondance de modèle (Caml, SML, Haskell).

Beaucoup d’autres réponses ont mis l’accent sur le côté performance (parallélisme) de la programmation fonctionnelle, ce qui, à mon avis, est très important. Cependant, vous avez spécifiquement posé une question sur la productivité: vous pouvez programmer la même chose plus rapidement dans un paradigme fonctionnel que dans un paradigme impératif.

En fait, j’ai constaté (d’expérience personnelle) que la programmation en F # correspond à ma façon de penser, et c’est donc plus facile. Je pense que c'est la plus grande différence. J'ai programmé à la fois en F # et en C #, et il y a beaucoup moins de & "Combattre le langage &"; en fa #, que j'adore. Vous n'avez pas à penser aux détails en F #. Voici quelques exemples de ce que j’ai trouvé qui me plait vraiment.

Par exemple, même si F # est typé de manière statique (tous les types sont résolus au moment de la compilation), l'inférence de type détermine quels types vous avez, de sorte que vous n'avez pas à le dire. Et s'il ne peut pas le comprendre, cela rend automatiquement votre fonction / classe / générique. Donc, vous n’avez jamais à écrire de générique, c’est tout automatique. Je trouve que cela signifie que je passe plus de temps à réfléchir au problème et moins à la manière de le mettre en œuvre. En fait, chaque fois que je reviens en C #, je trouve que cette inférence de type me manque vraiment, vous ne réalisez jamais à quel point c'est gênant tant que vous n'en avez plus besoin.

Également dans 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 va passer à travers et qui correspond à quelque chose (je ne me souviens plus de quoi, ça vient d'un puzzle du projet Euler):

let matchingFactors =
    factors
    |> Seq.filter (fun x -> largestPalindrome % x = 0)
    |> Seq.map (fun x -> (x, largestPalindrome / x))

Je réalise que faire un filtre puis une carte (c'est-à-dire une conversion de chaque élément) en C # serait assez simple, mais vous devez penser à un niveau inférieur. En particulier, vous devez écrire la boucle elle-même et avoir votre propre déclaration if explicite, et ce genre de choses. Depuis que j'ai appris le 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, écrivez " map " ;, au lieu d'implémenter chacun des détails.

J'aime aussi le | > qui, à mon avis, sépare F # de ocaml et éventuellement d’autres langages fonctionnels. C'est l'opérateur de pipe, il vous permet & Quot; pipe & Quot; la sortie d'une expression dans l'entrée d'une autre expression. Cela fait que le code suive ma pensée. Comme dans l'extrait de code ci-dessus, cela signifie que & "Prenez la séquence de facteurs, filtrez-la, puis mappez-la. &"; C'est un niveau de pensée très élevé, que vous n'obtenez pas dans un langage de programmation impératif, car vous êtes trop occupé à écrire la boucle et les instructions if. C'est la seule 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 # car vous pouvez penser à un niveau supérieur. Je dirais que, parce que les petits détails sont supprimés de la programmation fonctionnelle (au moins en F #), je suis plus productif.

Modifier : dans l'un des commentaires, vous avez demandé un exemple de " state " dans un langage de programmation fonctionnel. F # peut être écrit impérativement, alors voici un exemple direct de la façon dont vous pouvez avoir un état mutable en F #:

let mutable x = 5
for i in 1..10 do
    x <- x + i

Prenez en compte tous les bugs difficiles que vous avez passés longtemps à déboguer.

Maintenant, combien de ces bogues étaient dus à & "; interactions inattendues &"; entre deux composantes distinctes d'un programme? (Presque tous les bogues de threading ont cette forme: courses impliquant l'écriture de données partagées, des blocages, ... De plus, il est courant de trouver des bibliothèques qui ont un effet inattendu sur l'état global ou de lire / écrire le registre / l'environnement, etc.) < em> je dirais qu'au moins un «bogue dure sur trois» appartient à cette catégorie.

Maintenant, si vous passez à la programmation sans état / immuable / pure, tous ces bugs disparaissent. On vous présente plutôt de nouveaux défis (par exemple lorsque vous voulez différents modules pour interagir avec l'environnement), mais dans un langage comme Haskell, ces interactions sont explicitement réifiées dans le système de types, ce qui signifie peut simplement regarder le type d’une fonction et expliquer le type d’interactions qu’elle peut avoir avec le reste du programme.

C'est le gros gain de "l'immuabilité" de l'OMI. Dans un monde idéal, nous concevrions tous de superbes API et même lorsque les choses changeraient, les effets seraient locaux et bien documentés et les interactions «inattendues» seraient réduites au minimum. Dans le monde réel, de nombreuses API interagissent de multiples façons avec l'état global, et 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.

L'un des avantages 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 étant sans état afin d’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, car tout ce qui l’affecte est explicitement indiqué. Et lors de la programmation dans un langage fonctionnel, on a l'habitude de créer aussi peu de fonctions & "Dirty &"; (avec E / S, etc.) autant que possible. Séparer les éléments avec état de cette manière est un bon moyen de concevoir des programmes, même dans des langages peu fonctionnels.

Les langages fonctionnels peuvent prendre un certain temps pour & "obtenir &"; et il est difficile d'expliquer à quelqu'un qui n'a pas suivi ce processus. Mais la plupart des gens qui persistent assez longtemps se rendent finalement compte que le traitement en vaut la chandelle, même s’ils ne finissent pas par utiliser des langages fonctionnels.

Sans état, il est très facile de paralléliser automatiquement votre code (comme les processeurs sont conçus avec de plus en plus de cœurs, c’est très important).

J'ai récemment écrit un article sur ce sujet: Sur l'importance de la pureté .

Les applications Web sans état sont essentielles lorsque vous commencez à avoir un trafic plus important.

Par exemple, il est possible que de nombreuses données utilisateur ne soient pas stockées côté client. Dans ce cas, vous devez le stocker côté serveur. Vous pouvez utiliser la session par défaut des applications Web, mais si vous avez plusieurs instances de l’application, vous devez 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 collantes» dans lesquelles l’équilibreur de charge sait en quelque sorte à quel serveur envoyer la demande des utilisateurs. Ce n’est pas idéal, par exemple, cela signifie que 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. Ces jours-ci, il existe une foule de produits Nosql disponibles à cet effet (redis, mongo, elasticsearch, memcached). De cette façon, les serveurs Web sont sans état, mais vous avez toujours l'é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 impacter les utilisateurs.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top