J'ai lu beaucoup de choses sur la programmation fonctionnelle récemment, et je peux comprendre la plupart, mais la seule chose que je ne peux pas comprendre est le codage sans état. Il me semble que simplifier la programmation en supprimant l'état mutable, c'est comme "simplifier" une voiture en supprimant le tableau de bord: le produit fini peut être plus simple, mais bonne chance en le faisant interagir avec les utilisateurs finaux.
Presque toutes les applications utilisateur auxquelles je peux penser impliquent l’état comme concept de base. Si vous écrivez un document (ou une publication SO), l'état change à chaque nouvelle entrée. Ou si vous jouez à un jeu vidéo, il y a des tonnes de variables d'état, en commençant par les positions de tous les personnages, qui ont tendance à se déplacer constamment. Comment pouvez-vous faire quelque chose d'utile sans suivre l'évolution des valeurs?
Chaque fois que je trouve quelque chose qui traite de ce problème, il est écrit dans des fonctions vraiment techniques qui supposent un arrière-plan de FP lourd que je n'ai pas. Quelqu'un sait-il comment expliquer cela à quelqu'un qui a une bonne et solide compréhension du codage impératif mais qui est un n00b complet du côté fonctionnel?
EDIT: Un tas de réponses semblent jusqu'à présent essayer de me convaincre des avantages des valeurs immuables. Je comprends cette partie. C'est parfaitement logique. Ce que je ne comprends pas, c'est comment vous pouvez suivre les valeurs qui doivent changer, et changer constamment, sans variables mutables.
la source
Réponses:
Si vous êtes intéressé, voici une série d'articles qui décrivent la programmation de jeux avec Erlang.
Vous ne serez probablement pas comme cette réponse, mais vous n'obtenir programme fonctionnel jusqu'à ce que vous l' utilisez. Je peux poster des exemples de code et dire "Ici, ne voyez- vous pas " - mais si vous ne comprenez pas la syntaxe et les principes sous-jacents, alors vos yeux se glacent. De votre point de vue, il semble que je fasse la même chose qu'un langage impératif, mais simplement en établissant toutes sortes de frontières pour rendre la programmation plus difficile. Mon point de vue, vous vivez juste le paradoxe Blub .
J'étais sceptique au début, mais j'ai sauté dans le train de la programmation fonctionnelle il y a quelques années et j'en suis tombé amoureux. L'astuce avec la programmation fonctionnelle est de pouvoir reconnaître des modèles, des affectations de variables particulières et déplacer l'état impératif vers la pile. Une boucle for, par exemple, devient récursive:
Ce n'est pas très joli, mais nous avons eu le même effet sans mutation. Bien sûr, dans la mesure du possible, nous aimons éviter de boucler complètement et simplement l'abstraire:
La méthode Seq.iter énumérera la collection et invoquera la fonction anonyme pour chaque élément. Très utile :)
Je sais, l'impression des chiffres n'est pas vraiment impressionnante. Cependant, nous pouvons utiliser la même approche avec les jeux: maintenir tous les états dans la pile et créer un nouvel objet avec nos modifications dans l'appel récursif. De cette façon, chaque image est un instantané sans état du jeu, où chaque image crée simplement un nouvel objet avec les changements souhaités de tout objet sans état qui doit être mis à jour. Le pseudocode pour cela pourrait être:
Les versions impératives et fonctionnelles sont identiques, mais la version fonctionnelle n'utilise clairement aucun état mutable. Le code fonctionnel conserve tout l'état sur la pile - la bonne chose à propos de cette approche est que, si quelque chose ne va pas, le débogage est facile, tout ce dont vous avez besoin est une trace de pile.
Cela augmente jusqu'à n'importe quel nombre d'objets dans le jeu, car tous les objets (ou collections d'objets associés) peuvent être rendus dans leur propre thread.
Dans les langages fonctionnels, plutôt que de muter l'état des objets, nous renvoyons simplement un nouvel objet avec les changements que nous voulons. C'est plus efficace qu'il n'y paraît. Les structures de données, par exemple, sont très faciles à représenter comme des structures de données immuables. Les piles, par exemple, sont notoirement faciles à mettre en œuvre:
Le code ci-dessus construit deux listes immuables, les ajoute ensemble pour créer une nouvelle liste et ajoute les résultats. Aucun état modifiable n'est utilisé dans l'application. Il semble un peu volumineux, mais c'est uniquement parce que C # est un langage verbeux. Voici le programme équivalent en F #:
Aucun mutable nécessaire pour créer et manipuler des listes. Presque toutes les structures de données peuvent être facilement converties en leurs équivalents fonctionnels. J'ai écrit une page ici qui fournit des implémentations immuables de piles, files d'attente, tas de gauche, arbres rouge-noir, listes paresseuses. Pas un seul extrait de code ne contient d'état mutable. Pour "muter" un arbre, j'en crée un tout nouveau avec le nouveau nœud que je veux - c'est très efficace car je n'ai pas besoin de faire une copie de chaque nœud de l'arbre, je peux réutiliser les anciens dans mon nouveau arbre.
En utilisant un exemple plus significatif, j'ai également écrit cet analyseur SQL qui est totalement sans état (ou du moins mon code est sans état, je ne sais pas si la bibliothèque de lexing sous-jacente est sans état).
La programmation sans état est tout aussi expressive et puissante que la programmation avec état, elle nécessite juste un peu de pratique pour vous entraîner à commencer à penser sans état. Bien sûr, "la programmation sans état lorsque cela est possible, la programmation avec état si nécessaire" semble être la devise de la plupart des langages fonctionnels impurs. Il n'y a aucun mal à se rabattre sur les mutables lorsque l'approche fonctionnelle n'est tout simplement pas aussi propre ou efficace.
la source
Réponse courte: vous ne pouvez pas.
Alors, quel est le problème de l'immuabilité alors?
Si vous connaissez bien le langage impératif, alors vous savez que "les globaux sont mauvais". Pourquoi? Parce qu'ils introduisent (ou ont le potentiel d'introduire) des dépendances très difficiles à démêler dans votre code. Et les dépendances ne sont pas bonnes; vous voulez que votre code soit modulaire . Certaines parties du programme n'influencent pas le moins possible les autres parties. Et FP vous amène au Saint Graal de la modularité: pas d'effets secondaires du tout . Vous avez juste votre f (x) = y. Mettez x dedans, sortez y. Aucun changement à x ou à quoi que ce soit d'autre. La FP vous fait arrêter de penser à l'état et commencer à penser en termes de valeurs. Toutes vos fonctions reçoivent simplement des valeurs et produisent de nouvelles valeurs.
Cela présente plusieurs avantages.
Tout d'abord, aucun effet secondaire signifie des programmes plus simples, plus faciles à raisonner. Pas d'inquiétude que l'introduction d'une nouvelle partie du programme va interférer et planter une partie existante et fonctionnelle.
Deuxièmement, cela rend le programme trivialement parallélisable (une parallélisation efficace est une autre affaire).
Troisièmement, il existe des avantages de performances possibles. Disons que vous avez une fonction:
Maintenant, vous mettez une valeur de 3 in et vous obtenez une valeur de 6 out. À chaque fois. Mais vous pouvez aussi le faire impérativement, non? Oui. Mais le problème est que, impérativement, vous pouvez faire encore plus . Je peux faire:
mais je pourrais aussi faire
Le compilateur impératif ne sait pas si je vais avoir des effets secondaires ou non, ce qui le rend plus difficile à optimiser (c'est-à-dire que le double 2 n'a pas besoin d'être 4 à chaque fois). Le fonctionnel sait que je ne le ferai pas - par conséquent, il peut optimiser chaque fois qu'il voit "double 2".
Maintenant, même si la création de nouvelles valeurs à chaque fois semble incroyablement gaspilleuse pour des types complexes de valeurs en termes de mémoire informatique, il ne doit pas en être ainsi. Parce que, si vous avez f (x) = y, et que les valeurs x et y sont "essentiellement les mêmes" (par exemple, des arbres qui ne diffèrent que par quelques feuilles), alors x et y peuvent partager des parties de la mémoire - car aucun d'eux ne mute .
Donc, si cette chose immuable est si grande, pourquoi ai-je répondu que vous ne pouvez rien faire d'utile sans état mutable. Eh bien, sans mutabilité, tout votre programme serait une fonction f (x) = y géante. Et il en irait de même pour toutes les parties de votre programme: juste les fonctions, et les fonctions dans le sens "pur" de cela. Comme je l'ai dit, cela signifie que f (x) = y à chaque fois. Ainsi, par exemple, readFile ("monFichier.txt") devrait renvoyer la même valeur de chaîne à chaque fois. Pas trop utile.
Par conséquent, chaque FP fournit un moyen de muter l'état. Les langages fonctionnels «purs» (par exemple Haskell) le font en utilisant des concepts quelque peu effrayants tels que les monades, tandis que ceux «impurs» (par exemple ML) le permettent directement.
Et bien sûr, les langages fonctionnels sont livrés avec une foule d'autres avantages qui rendent la programmation plus efficace, comme des fonctions de première classe, etc.
la source
int double(x){ return x * (++y); }
que l'actuel sera toujours 4, bien qu'ayant toujours un effet secondaire non++y
Notez que dire que la programmation fonctionnelle n'a pas «d'état» est un peu trompeur et pourrait être la cause de la confusion. Il n'a certainement aucun «état mutable», mais il peut encore avoir des valeurs qui sont manipulées; ils ne peuvent tout simplement pas être modifiés sur place (par exemple, vous devez créer de nouvelles valeurs à partir des anciennes valeurs).
Il s'agit d'une simplification excessive, mais imaginez que vous disposiez d'un langage OO, où toutes les propriétés des classes sont définies une seule fois dans le constructeur, toutes les méthodes sont des fonctions statiques. Vous pouvez toujours effectuer à peu près n'importe quel calcul en demandant aux méthodes de prendre des objets contenant toutes les valeurs dont ils ont besoin pour leurs calculs, puis de renvoyer de nouveaux objets avec le résultat (peut-être même une nouvelle instance du même objet).
Il peut être «difficile» de traduire le code existant dans ce paradigme, mais c'est parce qu'il nécessite vraiment une façon complètement différente de penser le code. Comme effet secondaire, dans la plupart des cas, vous obtenez gratuitement de nombreuses possibilités de parallélisme.
Addendum: (Concernant votre modification de la façon de garder une trace des valeurs qui doivent changer)
Elles seraient stockées dans une structure de données immuable bien sûr ...
Ce n'est pas une `` solution '' suggérée, mais le moyen le plus simple de voir que cela fonctionnera toujours est que vous pouvez stocker ces valeurs immuables dans une structure de type carte (dictionnaire / table de hachage), saisie par un `` nom de variable ''.
Évidemment, dans les solutions pratiques, vous utiliseriez une approche plus saine, mais cela montre que dans le pire des cas, si rien d'autre ne fonctionnait, vous pouviez `` simuler '' un état mutable avec une telle carte que vous portiez dans votre arbre d'invocation.
la source
Je pense qu'il y a un léger malentendu. Les programmes fonctionnels purs ont un état. La différence réside dans la façon dont cet état est modélisé. Dans la programmation fonctionnelle pure, l'état est manipulé par des fonctions qui prennent un certain état et retournent l'état suivant. Le séquençage à travers les états est ensuite réalisé en passant l'état à travers une séquence de fonctions pures.
Même un état mutable global peut être modélisé de cette façon. À Haskell, par exemple, un programme est une fonction d'un monde à un monde. Autrement dit, vous passez dans l'univers entier , et le programme renvoie un nouvel univers. En pratique, cependant, il vous suffit de passer dans les parties de l'univers qui intéressent réellement votre programme. Et les programmes renvoient en fait une séquence d'actions qui servent d'instructions pour l'environnement d'exploitation dans lequel le programme s'exécute.
Vous vouliez voir cela expliqué en termes de programmation impérative. OK, regardons une programmation impérative vraiment simple dans un langage fonctionnel.
Considérez ce code:
Code impératif assez standard. Ne fait rien d'intéressant, mais c'est OK pour l'illustration. Je pense que vous conviendrez qu'il y a un État impliqué ici. La valeur de la variable x change avec le temps. Maintenant, modifions légèrement la notation en inventant une nouvelle syntaxe:
Mettez des parenthèses pour clarifier ce que cela signifie:
Donc, vous voyez, l'état est modélisé par une séquence d'expressions pures qui lient les variables libres des expressions suivantes.
Vous constaterez que ce modèle peut modéliser n'importe quel type d'état, même IO.
la source
Voici comment écrire du code sans état mutable : au lieu de mettre l'état changeant dans des variables mutables, vous le mettez dans les paramètres des fonctions. Et au lieu d'écrire des boucles, vous écrivez des fonctions récursives. Ainsi, par exemple, ce code impératif:
devient ce code fonctionnel (syntaxe de type schéma):
ou ce code Haskellish
Quant à savoir pourquoi les programmeurs fonctionnels aiment faire cela (ce que vous n'avez pas demandé), 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 des apatrides ne réside pas dans l'apatridie (ou la pureté) en soi , mais dans la capacité qu'il vous donne d'écrire des fonctions puissantes et réutilisables et de les combiner.
Vous pouvez trouver un bon tutoriel avec de nombreux exemples dans l'article de John Hughes, Why Functional Programming Matters .
la source
Ce sont juste des façons différentes de faire la même chose.
Prenons un exemple simple tel que l'ajout des nombres 3, 5 et 10. Imaginez penser à faire cela en changeant d'abord la valeur de 3 en y ajoutant 5, puis en ajoutant 10 à ce "3", puis en sortant la valeur actuelle de " 3 "(18). Cela semble manifestement ridicule, mais c'est essentiellement la façon dont la programmation impérative basée sur l'État est souvent effectuée. En effet, vous pouvez avoir de nombreux "3" différents qui ont la valeur 3, mais qui sont différents. Tout cela semble étrange, car nous sommes tellement ancrés dans l'idée, tout à fait extrêmement sensée, que les chiffres sont immuables.
Pensez maintenant à ajouter 3, 5 et 10 lorsque vous considérez que les valeurs sont immuables. Vous ajoutez 3 et 5 pour produire une autre valeur, 8, puis vous ajoutez 10 à cette valeur pour produire encore une autre valeur, 18.
Ce sont des façons équivalentes de faire la même chose. Toutes les informations nécessaires existent dans les deux méthodes, mais sous différentes formes. Dans l'un, les informations existent en tant qu'état et dans les règles de changement d'état. Dans l'autre, les informations existent dans des données immuables et des définitions fonctionnelles.
la source
Je suis en retard à la discussion, mais je voulais ajouter quelques points pour les personnes qui ont du mal avec la programmation fonctionnelle.
D'abord la voie impérative (en pseudocode)
Maintenant la voie fonctionnelle (en pseudocode). Je m'appuie fortement sur l'opérateur ternaire parce que je veux que les gens issus de milieux impératifs puissent réellement lire ce code. Donc, si vous n'utilisez pas beaucoup l'opérateur ternaire (je l'ai toujours évité à mes jours impératifs), voici comment cela fonctionne.
Vous pouvez enchaîner l'expression ternaire en mettant une nouvelle expression ternaire à la place de la fausse expression
Donc, avec cela à l'esprit, voici la version fonctionnelle.
Ceci est un exemple trivial. Si cela faisait bouger les gens dans un monde de jeu, vous devriez introduire des effets secondaires comme dessiner la position actuelle de l'objet sur l'écran et introduire un peu de retard dans chaque appel en fonction de la vitesse à laquelle l'objet se déplace. Mais vous n'auriez toujours pas besoin d'un état mutable.
La leçon est que les langages fonctionnels "mutent" en appelant la fonction avec différents paramètres. Évidemment, cela ne mute pas vraiment les variables, mais c'est ainsi que vous obtenez un effet similaire. Cela signifie que vous devrez vous habituer à penser récursivement si vous voulez faire de la programmation fonctionnelle.
Apprendre à penser récursivement n'est pas difficile, mais cela prend à la fois de la pratique et une boîte à outils. Cette petite section de ce livre "Learn Java" où ils ont utilisé la récursivité pour calculer la factorielle ne la coupe pas. Vous avez besoin d'une boîte à outils de compétences telles que la création de processus itératifs à partir de la récursivité (c'est pourquoi la récursivité de la queue est essentielle pour le langage fonctionnel), des continuations, des invariants, etc. Vous ne feriez pas de programmation OO sans vous renseigner sur les modificateurs d'accès, les interfaces, etc. Même chose pour la programmation fonctionnelle.
Ma recommandation est de faire le Little Schemer (notez que je dis "faire" et non "lire") puis faire tous les exercices du SICP. Lorsque vous avez terminé, vous aurez un cerveau différent de celui où vous avez commencé.
la source
Il est en fait assez facile d'avoir quelque chose qui ressemble à un état mutable même dans les langues sans état mutable.
Considérons une fonction de type
s -> (a, s)
. Traduisant de la syntaxe Haskell, cela signifie une fonction qui prend un paramètre de type "s
" et renvoie une paire de valeurs, de types "a
" et "s
". Sis
est le type de notre état, cette fonction prend un état et renvoie un nouvel état, et éventuellement une valeur (vous pouvez toujours retourner "unit" aka()
, qui est en quelque sorte équivalent à "void
" en C / C ++, comme le "a
" type). Si vous enchaînez plusieurs appels de fonctions avec des types comme celui-ci (obtenir l'état retourné d'une fonction et le passer à la suivante), vous avez un état "mutable" (en fait, vous êtes dans chaque fonction créant un nouvel état et abandonnant l'ancien) ).Il pourrait être plus facile à comprendre si vous imaginez l'état mutable comme «l'espace» où votre programme s'exécute, puis pensez à la dimension temporelle. A l'instant t1, "l'espace" est dans une certaine condition (disons par exemple qu'un emplacement mémoire a la valeur 5). À un instant ultérieur t2, il est dans un état différent (par exemple, cet emplacement de mémoire a maintenant la valeur 10). Chacune de ces "tranches" de temps est un état, et il est immuable (vous ne pouvez pas remonter dans le temps pour les changer). Donc, de ce point de vue, vous êtes passé de l'espace-temps complet avec une flèche de temps (votre état mutable) à un ensemble de tranches d'espace-temps (plusieurs états immuables), et votre programme traite simplement chaque tranche comme une valeur et calcule chaque d’eux en fonction de la précédente.
OK, ce n'était peut-être pas plus facile à comprendre :-)
Il peut sembler inefficace de représenter explicitement l'ensemble de l'état du programme comme une valeur, qui doit être créée uniquement pour être supprimée à l'instant suivant (juste après la création d'un nouveau). Pour certains algorithmes, cela peut être naturel, mais dans le cas contraire, il existe une autre astuce. Au lieu d'un état réel, vous pouvez utiliser un faux état qui n'est rien de plus qu'un marqueur (appelons le type de ce faux état
State#
). Ce faux état existe du point de vue du langage et est transmis comme toute autre valeur, mais le compilateur l'omet complètement lors de la génération du code machine. Il ne sert qu'à marquer la séquence d'exécution.Par exemple, supposons que le compilateur nous donne les fonctions suivantes:
La traduction à partir de ces déclarations de type Haskell,
readRef
reçoit quelque chose qui ressemble à un pointeur ou à une poignée à une valeur de type "a
", et au faux état, et renvoie la valeur de type "a
" pointée par le premier paramètre et un nouvel faux état.writeRef
est similaire, mais modifie la valeur pointée à la place.Si vous appelez
readRef
puis passez le faux état renvoyé parwriteRef
(peut-être avec d'autres appels à des fonctions non liées au milieu; ces valeurs d'état créent une "chaîne" d'appels de fonction), il renverra la valeur écrite. Vous pouvez appeler àwriteRef
nouveau avec le même pointeur / poignée et il écrira au même emplacement mémoire - mais, comme il renvoie conceptuellement un nouvel (faux) état, le (faux) état est toujours imitable (un nouveau a été "créé" "). Le compilateur appellera les fonctions dans l'ordre où il devrait les appeler s'il y avait une variable d'état réel qui devait être calculée, mais le seul état qui existe est l'état complet (modifiable) du matériel réel.(Ceux qui savent Haskell remarquerez que je simplifié les choses beaucoup et ommited plusieurs détails importants. Pour ceux qui veulent voir plus de détails, jetez un oeil à
Control.Monad.State
de lamtl
, et auxST s
etIO
(akaST RealWorld
) monades.)Vous vous demandez peut-être pourquoi le faire de manière si détournée (au lieu d'avoir simplement un état mutable dans la langue). Le véritable avantage est que vous avez réifié l'état de votre programme. Ce qui était auparavant implicite (l'état de votre programme était global, permettant des choses comme l' action à distance ) est maintenant explicite. Les fonctions qui ne reçoivent pas et ne retournent pas l'état ne peuvent pas le modifier ni en être influencées; ils sont "purs". Encore mieux, vous pouvez avoir des threads d'état séparés, et avec un peu de type magique, ils peuvent être utilisés pour intégrer un calcul impératif dans un calcul pur, sans le rendre impur (la
ST
monade de Haskell est celle normalement utilisée pour cette astuce; leState#
I mentionné ci-dessus est en fait GHCState# s
, utilisé par sa mise en œuvre de laST
etIO
monades).la source
La programmation fonctionnelle évite l' état et souligneFonctionnalité. Il n'y a jamais rien comme aucun état, bien que l'état puisse en fait être quelque chose d'immuable ou intégré dans l'architecture de ce avec quoi vous travaillez. Considérez la différence entre un serveur Web statique qui charge simplement des fichiers du système de fichiers et un programme qui implémente un cube Rubik. Le premier va être implémenté en termes de fonctions conçues pour transformer une demande en une demande de chemin de fichier en une réponse du contenu de ce fichier. Pratiquement aucun état n'est nécessaire au-delà d'un tout petit peu de configuration (l '«état» du système de fichiers est vraiment hors de portée du programme. Le programme fonctionne de la même manière quel que soit l'état des fichiers). Dans ce dernier cas, vous devez modéliser le cube et l'implémentation de votre programme de la façon dont les opérations sur ce cube changent son état.
la source
En plus des excellentes réponses que d'autres donnent, pensez aux classes
Integer
etString
en Java. Les instances de ces classes sont immuables, mais cela ne rend pas les classes inutiles simplement parce que leurs instances ne peuvent pas être modifiées. L'immuabilité vous donne une certaine sécurité. Vous savez que si vous utilisez une instance String ou Integer comme clé de aMap
, la clé ne peut pas être modifiée. Comparez cela à laDate
classe en Java:Vous avez silencieusement changé une clé dans votre carte! Travailler avec des objets immuables, comme dans la programmation fonctionnelle, est beaucoup plus propre. Il est plus facile de raisonner sur les effets secondaires - aucun! Cela signifie que c'est plus facile pour le programmeur, et aussi plus facile pour l'optimiseur.
la source
Pour les applications hautement interactives telles que les jeux, la programmation réactive fonctionnelle est votre amie: si vous pouvez formuler les propriétés du monde de votre jeu sous forme de valeurs variant dans le temps (et / ou de flux d'événements), vous êtes prêt! Ces formules seront parfois encore plus naturelles et révélatrices d'intentions que la mutation d'un état, par exemple pour une balle en mouvement, vous pouvez directement utiliser la loi bien connue x = v * t . Et ce qui est mieux, les règles du jeu écrites de cette façon sont mieux composées que les abstractions orientées objet. Par exemple, dans ce cas, la vitesse de la balle peut également être une valeur variant dans le temps, qui dépend du flux d'événements constitué des collisions de la balle. Pour des considérations de conception plus concrètes, voir Création de jeux dans Elm .
la source
En utilisant un peu de créativité et de correspondance de motifs, des jeux sans état ont été créés:
ainsi que des démos roulantes:
et visualisations:
la source
C'est ainsi que FORTRAN fonctionnerait sans blocs COMMUNS: vous écririez des méthodes qui avaient les valeurs que vous avez passées et des variables locales. C'est tout.
La programmation orientée objet nous a rapprochés de l'état et du comportement, mais c'était une nouvelle idée lorsque je l'ai rencontrée pour la première fois en C ++ en 1994.
Décidément, j'étais un programmeur fonctionnel quand j'étais ingénieur en mécanique et je ne le savais pas!
la source
Gardez à l'esprit: les langages fonctionnels sont Turing complets. Par conséquent, toute tâche utile que vous effectuez dans un langage impératif peut être effectuée dans un langage fonctionnel. En fin de compte, je pense qu'il y a quelque chose à dire sur une approche hybride. Des langages comme F # et Clojure (et je suis sûr que d'autres) encouragent la conception sans état, mais permettent la mutabilité lorsque cela est nécessaire.
la source
Vous ne pouvez pas avoir un langage fonctionnel pur qui soit utile. Il y aura toujours un niveau de mutabilité auquel vous devrez faire face, IO en est un exemple.
Considérez les langages fonctionnels comme un autre outil que vous utilisez. C'est bon pour certaines choses, mais pas pour d'autres. L'exemple de jeu que vous avez donné n'est peut-être pas le meilleur moyen d'utiliser un langage fonctionnel, au moins l'écran aura un état modifiable que vous ne pouvez rien faire avec FP. La façon dont vous envisagez le problème et le type de problèmes que vous résolvez avec FP seront différents de ceux auxquels vous êtes habitué avec la programmation impérative.
la source
En utilisant beaucoup de récursivité.
Tic Tac Toe en F # (Un langage fonctionnel.)
la source
C’est très simple. Vous pouvez utiliser autant de variables que vous le souhaitez dans la programmation fonctionnelle ... mais seulement si ce sont des variables locales (contenues dans des fonctions). Il vous suffit donc d'envelopper votre code dans des fonctions, de transmettre des valeurs dans les deux sens (comme des paramètres passés et des valeurs renvoyées) ... et c'est tout!
Voici un exemple:
la source