L'innovation la plus apparente remarquée par les nouveaux venus à Haskell est qu'il existe une séparation entre le monde impur qui s'intéresse à la communication avec le monde extérieur et le monde pur du calcul et des algorithmes. Une question fréquente pour les débutants est "Comment puis-je me débarrasser IO
, c'est-à-dire se convertir IO a
en a
?" Le moyen d'y parvenir est d'utiliser des monades (ou d'autres abstractions) pour écrire du code qui effectue des effets d'E / S et des chaînes. Ce code rassemble des données du monde extérieur, en crée un modèle, effectue certains calculs, éventuellement en utilisant du code pur, et génère le résultat.
En ce qui concerne le modèle ci-dessus, je ne vois rien de mal à manipuler les interfaces graphiques dans la IO
monade. Le plus gros problème qui découle de ce style est que les modules ne sont plus composables, c'est-à-dire que je perds la plupart de mes connaissances sur l'ordre d'exécution global des instructions dans mon programme. Pour le récupérer, je dois appliquer un raisonnement similaire à celui du code GUI impératif simultané. Pendant ce temps, pour un code impur non GUI, l'ordre d'exécution est évident en raison de la définition de l' opérateur de la IO
monade >==
(au moins tant qu'il n'y a qu'un seul thread). Pour le code pur, cela n'a pas d'importance du tout, sauf dans les cas d'angle pour augmenter les performances ou pour éviter des évaluations entraînant⊥
.
La plus grande différence philosophique entre console et IO graphique est que les programmes implémentant le premier sont généralement écrits dans un style synchrone. Ceci est possible car il n'y a (en laissant de côté les signaux et autres descripteurs de fichiers ouverts) qu'une seule source d'événements: le flux d'octets communément appeléstdin
. Les interfaces graphiques sont cependant intrinsèquement asynchrones et doivent réagir aux événements du clavier et aux clics de souris.
Une philosophie populaire consistant à effectuer des E / S asynchrones de manière fonctionnelle est appelée programmation réactive fonctionnelle (FRP). Il a obtenu beaucoup de traction récemment dans des langages impurs et non fonctionnels grâce à des bibliothèques telles que ReactiveX et des frameworks tels que Elm. En un mot, c'est comme visualiser les éléments de l'interface graphique et d'autres choses (comme les fichiers, les horloges, les alarmes, le clavier, la souris) en tant que sources d'événements, appelées "observables", qui émettent des flux d'événements. Ces événements sont combinés à l' aide des opérateurs familiers tels que map
, foldl
, zip
, filter
, concat
, join
, etc., pour produire de nouveaux flux. Ceci est utile car l'état du programme lui-même peut être vu comme scanl . map reactToEvents $ zipN <eventStreams>
du programme, oùN
est égal au nombre d'observables jamais pris en compte par le programme.
Travailler avec des observables FRP permet de récupérer la composabilité car les événements d'un flux sont ordonnés dans le temps. La raison en est que l'abstraction du flux d'événements permet de visualiser tous les observables sous forme de boîtes noires. En fin de compte, la combinaison de flux d'événements à l'aide d'opérateurs redonne un ordre local lors de l'exécution. Cela m'oblige à être beaucoup plus honnête sur les invariants sur lesquels mon programme s'appuie réellement, de la même manière que toutes les fonctions de Haskell doivent être référentiellement transparentes: si je veux extraire des données d'une autre partie de mon programme, je dois être explicite annonce un type approprié pour mes fonctions. (La monade IO, étant un langage spécifique au domaine pour écrire du code impur, contourne efficacement cela)