Il s'agit d'une "interprétation" suggérée de la IO
monade. Si vous voulez prendre cette "interprétation" au sérieux, vous devez prendre au sérieux "RealWorld". action world
Peu importe qu'il soit évalué spéculativement ou non, action
n'a pas d'effets secondaires, ses effets, le cas échéant, sont gérés en renvoyant un nouvel état de l'univers où ces effets se sont produits, par exemple, un paquet réseau a été envoyé. Cependant, le résultat de la fonction est ((),world)
et donc le nouvel état de l'univers est world
. Nous n'utilisons pas le nouvel univers que nous pouvons avoir évalué spéculativement sur le côté. L'état de l'univers est world
.
Vous avez probablement du mal à prendre cela au sérieux. Il y a de nombreuses façons, c'est au mieux superficiellement paradoxal et absurde. La concurrence est particulièrement non évidente ou folle dans cette perspective.
"Attendez, attendez", dites-vous. " RealWorld
est juste un 'jeton'. Ce n'est pas réellement l'état de l'univers entier." D'accord, alors cette "interprétation" n'explique rien. Néanmoins, en tant que détail d'implémentation , c'est ainsi que les modèles GHC IO
. 1 Cependant, cela signifie que nous avons des «fonctions» magiques qui ont effectivement des effets secondaires et ce modèle ne donne aucune indication sur leur signification. Et, puisque ces fonctions ont en fait des effets secondaires, la préoccupation que vous soulevez est tout à fait pertinente. GHC doit faire tout son possible pour s'en assurer RealWorld
et ces fonctions spéciales ne sont pas optimisées de manière à changer le comportement prévu du programme.
Personnellement (comme cela est probablement évident maintenant), je pense que ce modèle de "passage du monde" IO
est tout simplement inutile et déroutant en tant qu'outil pédagogique. (Que ce soit utile pour la mise en œuvre, je ne sais pas. Pour GHC, je pense que c'est plus un artefact historique.)
Une autre approche consiste à considérer IO
comme une description des demandes avec des gestionnaires de réponse. Il existe plusieurs façons de procéder. Le plus accessible est probablement d'utiliser une construction de monade gratuite, en particulier nous pouvons utiliser:
data IO a = Return a | Request OSRequest (OSResponse -> IO a)
Il existe de nombreuses façons de le rendre plus sophistiqué et d'avoir des propriétés un peu meilleures, mais c'est déjà une amélioration. Il ne nécessite pas d'hypothèses philosophiques profondes sur la nature de la réalité pour comprendre. Tout ce qu'il dit, c'est que IO
c'est soit un programme trivial Return
qui ne fait que renvoyer une valeur, soit une demande au système d'exploitation avec un gestionnaire pour la réponse. OSRequest
peut être quelque chose comme:
data OSRequest = OpenFile FilePath | PutStr String | ...
De même, OSResponse
pourrait être quelque chose comme:
data OSResponse = Errno Int | OpenSucceeded Handle | ...
(L'une des améliorations qui peuvent être apportées est de rendre les choses plus sécuritaires pour que vous sachiez que vous n'obtiendrez pas OpenSucceeded
d'une PutStr
requête.) Ce modèle IO
décrit les requêtes qui sont interprétées par un système (pour la "vraie" IO
monade, c'est le runtime Haskell lui-même), puis, peut-être, ce système appellera le gestionnaire auquel nous avons fourni une réponse. Bien sûr, cela ne donne aucune indication sur la façon dont une demande PutStr "hello world"
doit être traitée, mais cela ne prétend pas non plus. Il indique clairement que cela est délégué à un autre système. Ce modèle est également assez précis. Tous les programmes utilisateur des systèmes d'exploitation modernes doivent demander au système d'exploitation de faire quoi que ce soit.
Ce modèle fournit les bonnes intuitions. Par exemple, de nombreux débutants considèrent des choses comme l' <-
opérateur comme "déballant" IO
, ou ont (malheureusement renforcé) des vues qu'un IO String
, disons, est un "conteneur" qui "contient" des String
(et <-
les sort ensuite). Cette vue demande-réponse rend cette perspective clairement fausse. Il n'y a aucun descripteur de fichier à l'intérieur de OpenFile "foo" (\r -> ...)
. Une analogie courante pour souligner cela est qu'il n'y a pas de gâteau à l'intérieur d'une recette de gâteau (ou peut-être que la "facture" serait mieux dans ce cas).
Ce modèle fonctionne également facilement avec la concurrence. Nous pouvons facilement avoir un constructeur pour OSRequest
like Fork :: (OSResponse -> IO ()) -> OSRequest
et ensuite le runtime peut entrelacer les requêtes produites par ce gestionnaire supplémentaire avec le gestionnaire normal comme bon lui semble. Avec une certaine intelligence, vous pouvez utiliser cela (ou des techniques connexes) pour modéliser des choses comme la concurrence plus directement plutôt que de simplement dire "nous faisons une demande au système d'exploitation et les choses se produisent." C'est ainsi que fonctionne la IOSpec
bibliothèque .
1 Hugs a utilisé une implémentation basée sur la continuation IO
qui est à peu près similaire à ce que je décris bien qu'avec des fonctions opaques au lieu d'un type de données explicite. HBC a également utilisé une implémentation basée sur la continuation superposée à l'ancienne E / S basée sur le flux de demande-réponse. NHC (et donc YHC) utilisait des thunks, c'est-à-dire à peu près IO a = () -> a
si le ()
était appelé World
, mais il ne faisait pas de passage d'état. JHC et UHC ont utilisé essentiellement la même approche que GHC.