Les langages impurs ne diffèrent pas vraiment en principe des langages impératifs plus familiers, surtout maintenant que de nombreuses astuces fonctionnelles ont été copiées. Ce qui est différent, c'est le style - comment résoudre les problèmes.
Que vous comptiez Haskell comme pur, ou que vous comptiez la monade IO comme impureté, le style Haskell est une forme extrême de ce style et mérite d'être étudié.
La monade Haskell IO est dérivée de la théorie mathématique des monades (bien sûr). Cependant, pour les programmeurs impératifs, je pense qu'une manière à l'envers d'arriver aux monades a plus de sens.
Phase un - un langage fonctionnel pur peut facilement renvoyer une grande valeur de chaîne comme résultat. Cette grosse chaîne peut être le code source d'un programme impératif, dérivé de manière purement fonctionnelle de certains paramètres spécifiant les exigences. Vous pouvez ensuite créer un compilateur de "niveau supérieur" qui exécute votre générateur de code, puis alimente automatiquement ce code généré dans le compilateur de langage impératif.
Phase deux - plutôt que de générer du code source textuel, vous générez un arbre de syntaxe abstraite fortement typé. Votre compilateur en langage impératif est absorbé dans votre compilateur de "niveau supérieur" et accepte l'AST directement comme code source. C'est beaucoup plus proche de ce que fait Haskell.
C'est toujours gênant, cependant. Par exemple, vous avez deux types de fonctions distinctes - celles évaluées pendant la phase de génération de code et celles exécutées lors de l'exécution du programme généré. C'est un peu comme la distinction entre les fonctions et les modèles en C ++.
Donc, pour la phase 3, rendez les deux identiques - la même fonction avec la même syntaxe peut être partiellement évaluée pendant la «génération de code», ou entièrement évaluée, ou pas du tout évaluée. En outre, jetez tous les nœuds AST de construction en boucle en faveur de la récursivité. En fait, rejetez l'idée des nœuds AST en tant que type spécial de données - ne disposez pas de nœuds AST à "valeur littérale", mais simplement de valeurs, etc.
C'est à peu près ce que fait la monade IO - l'opérateur de liaison est un moyen de composer des "actions" pour former des programmes. Ce n'est rien de spécial - juste une fonction. De nombreuses expressions et fonctions peuvent être évaluées lors de la "génération de code", mais celles qui dépendent des effets secondaires d'E / S doivent avoir une évaluation retardée jusqu'à l'exécution - non pas par une règle spéciale, mais comme une conséquence naturelle des dépendances des données dans expressions.
Les monades en général ne sont que de la généralisation - elles ont la même interface, mais implémentent les opérations abstraites différemment, donc au lieu d'évaluer une description de code impératif, elles évaluent plutôt autre chose. Avoir la même interface signifie qu'il y a certaines choses que vous pouvez faire aux monades sans vous soucier de quelle monade, ce qui s'avère utile.
Cette description fera sans aucun doute exploser les têtes des puristes, mais elle explique pour moi certaines des vraies raisons pour lesquelles Haskell est intéressant. Il brouille la frontière entre programmation et métaprogrammation, et utilise les outils de programmation fonctionnelle pour réinventer la programmation impérative sans avoir besoin d'une syntaxe particulière.
Une critique que j'ai des modèles C ++ est qu'ils sont une sorte de sous-langage fonctionnel pur cassé dans un langage impératif - pour évaluer la même fonction de base au moment de la compilation plutôt qu'au moment de l'exécution, vous devez la réimplémenter en utilisant un style complètement différent de codage. Dans Haskell, bien que l'impureté doive être étiquetée comme telle dans son type, la même fonction exacte peut être évaluée à la fois dans un sens de méta-programmation et dans un sens d'exécution hors méta-programmation dans le même programme - il n'y a pas de ligne dure entre programmation et métaprogrammation.
Cela dit, il existe des métaprogrammations que Haskell standard ne peut pas faire, essentiellement parce que les types (et peut-être quelques autres choses) ne sont pas des valeurs de première classe. Il existe cependant des variantes linguistiques qui tentent de résoudre ce problème.
Beaucoup de choses que j'ai dites sur Haskell peuvent être appliquées dans des langages fonctionnels impurs - et parfois même des langages impératifs. Haskell est différent parce que vous n'avez pas d'autre choix que d'adopter cette approche - cela vous oblige essentiellement à apprendre ce style de travail. Vous pouvez «écrire C en ML», mais vous ne pouvez pas «écrire C en Haskell» - du moins pas sans savoir ce qui se passe sous le capot.