L’équivalent Haskell d’une entrée comme input en Python est forcément impur.
Oui mais justement le gros intérêt de Haskell en comparaison avec d'autres langages comme OCaml par exemple, c'est qu'ils n'ont pas transigé sur la pureté pour intégrer les IOs. Donc on conserve toutes les propriétés liées à la pureté.
(Brièvement) pour ceux qui se demandent comment ça marche (j'avais présenté ça ici) :
Si on avait une fonction input qui renvoyait une chaîne en faisant un effet de bord (lecture de ce que tape l'utilisateur) et ayant pour type input :: String. Supposons qu'on fasse plusieurs lectures :
Comme Haskell est paresseux, prenom et nom seraient évalués seulement quand on aurait besoin d'afficher la chaîne (donc dans quel ordre ?) et age ne serait jamais évalué. Comme il y a la transparence référentielle et qu'on appelle input avec les mêmes paramètres (i.e. aucun), on peut dire que nom = prenom = age = input. Bref ça ne marche pas, d'autant plus que comme print ne renvoie rien, on n'a jamais besoin de l'évaluer donc il ne se passerait rien.
On s'en sort en disant que input prend en fait un paramètre, l'état du monde extérieur à Haskell et qu'en plus d'une chaîne il renvoie l'état du monde extérieur après avoir fait l'IO. Donc son type est en fait input :: RealWorld -> (RealWorld,String). De la même façon, on suppose que main reçoit l'état du monde extérieur avant le lancement du programme et renvoie l'état du monde extérieur après l'exécution. Maintenant il ne reste plus qu'à chaîner tout ça :
Donc là on est sûr que pour obtenir l'état du monde renvoyé par main, il faut évaluer print... w4, donc pour obtenir w4 il faut évaluer input w3 et ainsi de suite. Notez qu'on aurait pu faire autrement et dire que chaque fonction qui effectue une IO prend en paramètre une fonction qui va utiliser son résultat (continuation passing style, CPS) et on aurait obtenu Node.js, mais Haskell est un langage moderne qui a remplacé cette approche en 1992 (lancer de troll réussi ?).
On peut introduire un alias pour simplifier l'écriture des types :
typeIOa=RealWorld->(RealWorld,a)
Donc on aurait input :: IO String, print :: String -> IO () (où () veut dire void) et main :: IO ().
Le problème avec les variables world explicites est que si le programmeur fait n'importe quoi avec (s'il en utilise une plusieurs fois, etc.) ça n'est plus déterministe et c'est n'importe quoi. Donc soit on détecte statiquement que l'utilisateur fait n'importe quoi (ce qu'ils ont fait dans le langage Clean plus tard), soit on cache l'utilisation de ces variables en ne proposant que l'opérateur pour composer deux opérations qui font des IO (l'équivalent de ";" en C). Donc en Haskell c'est ça, on peut utiliser >>= pour composer deux opérations, avec (>>=) :: IO a -> (a -> IO b) -> IO b :
Le principe et la notation se généralisent à d'autres cas où on cherche à "chaîner des opérations" (cf Monad), c'est pour ça qu'on la retrouve dans l'exemple utilisant la mémoire transactionnelle dans mon commentaire plus haut alors qu'il n'y a pas d'IOs.
Bref au final c'est resté pur tout en faisant des IOs, c'est pour ça que certains parlent de "meilleur langage impératif du monde" comme le mentionne la dépêche. J'aurais dit "univers" mais bon. ;)
[^] # Re: cat troll.hs
Posté par hsyl20 (site web personnel) . En réponse à la dépêche Sortie du Glorious Haskell Compiler 7.8. Évalué à 10.
Oui mais justement le gros intérêt de Haskell en comparaison avec d'autres langages comme OCaml par exemple, c'est qu'ils n'ont pas transigé sur la pureté pour intégrer les IOs. Donc on conserve toutes les propriétés liées à la pureté.
(Brièvement) pour ceux qui se demandent comment ça marche (j'avais présenté ça ici) :
Si on avait une fonction
inputqui renvoyait une chaîne en faisant un effet de bord (lecture de ce que tape l'utilisateur) et ayant pour typeinput :: String. Supposons qu'on fasse plusieurs lectures :Comme Haskell est paresseux,
prenometnomseraient évalués seulement quand on aurait besoin d'afficher la chaîne (donc dans quel ordre ?) etagene serait jamais évalué. Comme il y a la transparence référentielle et qu'on appelleinputavec les mêmes paramètres (i.e. aucun), on peut dire quenom = prenom = age = input. Bref ça ne marche pas, d'autant plus que commeprintne renvoie rien, on n'a jamais besoin de l'évaluer donc il ne se passerait rien.On s'en sort en disant que
inputprend en fait un paramètre, l'état du monde extérieur à Haskell et qu'en plus d'une chaîne il renvoie l'état du monde extérieur après avoir fait l'IO. Donc son type est en faitinput :: RealWorld -> (RealWorld,String). De la même façon, on suppose quemainreçoit l'état du monde extérieur avant le lancement du programme et renvoie l'état du monde extérieur après l'exécution. Maintenant il ne reste plus qu'à chaîner tout ça :Donc là on est sûr que pour obtenir l'état du monde renvoyé par
main, il faut évaluerprint... w4, donc pour obtenirw4il faut évaluerinput w3et ainsi de suite. Notez qu'on aurait pu faire autrement et dire que chaque fonction qui effectue une IO prend en paramètre une fonction qui va utiliser son résultat (continuation passing style, CPS) et on aurait obtenu Node.js, mais Haskell est un langage moderne qui a remplacé cette approche en 1992 (lancer de troll réussi ?).On peut introduire un alias pour simplifier l'écriture des types :
Donc on aurait
input :: IO String,print :: String -> IO ()(où()veut direvoid) etmain :: IO ().Le problème avec les variables
worldexplicites est que si le programmeur fait n'importe quoi avec (s'il en utilise une plusieurs fois, etc.) ça n'est plus déterministe et c'est n'importe quoi. Donc soit on détecte statiquement que l'utilisateur fait n'importe quoi (ce qu'ils ont fait dans le langage Clean plus tard), soit on cache l'utilisation de ces variables en ne proposant que l'opérateur pour composer deux opérations qui font des IO (l'équivalent de ";" en C). Donc en Haskell c'est ça, on peut utiliser>>=pour composer deux opérations, avec(>>=) :: IO a -> (a -> IO b) -> IO b:Et comme c'est très moche, on utilise l'équivalent avec du sucre syntaxique (do-notation) :
Le principe et la notation se généralisent à d'autres cas où on cherche à "chaîner des opérations" (cf Monad), c'est pour ça qu'on la retrouve dans l'exemple utilisant la mémoire transactionnelle dans mon commentaire plus haut alors qu'il n'y a pas d'IOs.
Bref au final c'est resté pur tout en faisant des IOs, c'est pour ça que certains parlent de "meilleur langage impératif du monde" comme le mentionne la dépêche. J'aurais dit "univers" mais bon. ;)