Question

Quelqu'un pourrait-il donner quelques indications sur la raison pour laquelle les calculs impurs dans Haskell sont modélisés comme des monades?

Je veux dire monade est juste une interface avec 4 opérations, alors quel était le raisonnement à la modélisation des effets secondaires en elle?

Était-ce utile?

La solution

Supposons une fonction a des effets secondaires. Si nous prenons tous les effets qu'elle produit que les paramètres d'entrée et de sortie, la fonction est pure au monde extérieur.

Donc, pour une fonction impure

f' :: Int -> Int

nous ajoutons le VraiMonde à l'examen

f :: Int -> RealWorld -> (Int, RealWorld)
-- input some states of the whole world,
-- modify the whole world because of the side effects,
-- then return the new world.

alors f est à nouveau pur. Nous définissons un type de données IO a = RealWorld -> (a, RealWorld) paramétrisé, donc on n'a pas besoin de taper RealWorld tant de fois

f :: Int -> IO Int

Pour le programmeur, la manipulation d'un RealWorld particulier est directement aussi dans des conditions dangereuses, si un programmeur obtient la main sur une valeur de type RealWorld, ils pourraient essayer de Copier , ce qui est pratiquement impossible. (Pensez à essayer de copier le système de fichiers entier, par exemple. Où voulez-vous dire?) Par conséquent, notre définition de IO encapsule les états du monde entier ainsi.

Ces fonctions impurs sont inutiles si nous ne pouvons pas les enchaîner ensemble. Considérez

getLine :: IO String               = RealWorld -> (String, RealWorld)
getContents :: String -> IO String = String -> RealWorld -> (String, RealWorld)
putStrLn :: String -> IO ()        = String -> RealWorld -> ((), RealWorld)

Nous voulons obtenir un nom de fichier à partir de la console, lisez ce fichier, puis imprimer le contenu sur. Comment ferions-nous si nous pouvons accéder aux états du monde réel?

printFile :: RealWorld -> ((), RealWorld)
printFile world0 = let (filename, world1) = getLine world0
                       (contents, world2) = (getContents filename) world1 
                   in  (putStrLn contents) world2 -- results in ((), world3)

Nous voyons un modèle ici: les fonctions sont appelées comme ceci:

...
(<result-of-f>, worldY) = f worldX
(<result-of-g>, worldZ) = g <result-of-f> worldY
...

On pourrait donc définir un ~~~ opérateur pour les lier:

(~~~) :: (IO b) -> (b -> IO c) -> IO c

(~~~) ::      (RealWorld -> (b, RealWorld))
      -> (b -> RealWorld -> (c, RealWorld))
      ->       RealWorld -> (c, RealWorld)
(f ~~~ g) worldX = let (resF, worldY) = f worldX in
                        g resF worldY

alors nous pourrions simplement écrire

printFile = getLine ~~~ getContents ~~~ putStrLn

sans toucher le monde réel.


Supposons maintenant que nous voulons rendre le contenu de fichiers en majuscules ainsi. Uppercasing est une pure fonction

upperCase :: String -> String

Mais pour le faire dans le monde réel, il doit retourner un IO String. Il est facile de soulever une telle fonction:

impureUpperCase :: String -> RealWorld -> (String, RealWorld)
impureUpperCase str world = (upperCase str, world)

peut être généralisée:

impurify :: a -> IO a

impurify :: a -> RealWorld -> (a, RealWorld)
impurify a world = (a, world)

pour que impureUpperCase = impurify . upperCase, et nous pouvons écrire

printUpperCaseFile = 
    getLine ~~~ getContents ~~~ (impurify . upperCase) ~~~ putStrLn

(Note: Normalement, nous écrivons getLine ~~~ getContents ~~~ (putStrLn . upperCase))


Maintenant, nous allons voir ce que nous avons fait:

  1. Nous avons défini un (~~~) :: IO b -> (b -> IO c) -> IO c opérateur qui enchaîne deux fonctions impures ensemble
  2. Nous avons défini un impurify :: a -> IO a fonction qui convertit une valeur pure impure.

Maintenant, nous faisons l'identification (>>=) = (~~~) et return = impurify, et voir? Nous avons une monade.


(Pour vérifier s'il est vraiment devraient être satisfaits d'une monade il y a quelques axiomes:

(1) return a >>= f = f a

  impurify a               = (\world -> (a, world))
 (impurify a ~~~ f) worldX = let (resF, worldY) = (\world -> (a, world)) worldX 
                             in f resF worldY
                           = let (resF, worldY) =            (a, worldX))       
                             in f resF worldY
                           = f a worldX

(2) f >>= return = f

  (f ~~~ impurify) a worldX = let (resF, worldY) = impuify a worldX 
                              in f resF worldY
                            = let (resF, worldY) = (a, worldX)     
                              in f resF worldY
                            = f a worldX

(3) f >>= (\x -> g x >>= h) = (f >>= g) >>= h

Exercice.)

Autres conseils

  

Quelqu'un pourrait-il donner quelques indications sur les raisons les calculs impure dans Haskell sont modélisés comme des monades?

Cette question contient une incompréhension généralisée. Et Impuretés Monad sont des notions indépendantes. Est Impureté pas modélisé par Monad. Au contraire, il y a quelques types de données, tels que IO, qui représentent le calcul impératif. Et pour certains de ces types, une infime fraction de leur interface correspond au modèle d'interface appelée « Monad ». De plus, il n'y a pas pur / fonctionnel / explication dénotation de IO (et il est peu probable connu comme l'un, compte tenu de la "bin péché" usage de IO), mais il y a l'histoire souvent dit au sujet World -> (a, World) étant le sens de IO a. Cette histoire ne peut pas honnêtement décrire IO, parce que IO soutient la concurrence et la non-déterminisme. L'histoire ne fonctionne même pas quand pour les calculs déterministes qui permettent une interaction mi-calcul avec le monde.

Pour plus d'explications, voir cette réponse .

Modifier : en relisant la question, je ne pense pas que ma réponse est tout à fait sur la bonne voie. Les modèles de calcul impératif ne tournent souvent être monades, tout comme la question dit. Le demandeur peut ne pas supposer que vraiment monadness de quelque manière que permet la modélisation du calcul impératif.

Si je comprends bien, quelqu'un a appelé Eugenio premier Moggi a remarqué que une construction mathématique précédemment obscure appelée « monade » pourrait être utilisé pour modéliser les effets secondaires dans les langages informatiques, et donc préciser leur sémantique en utilisant le calcul Lambda. Lorsque Haskell a été développé il y avait différentes façons dont les calculs impurs ont été modelés (voir Simon Peyton Jones

Comme vous le dites, est Monad une structure très simple. Une moitié de la réponse est: Monad est la structure la plus simple que l'on pourrait peut-être donner à côte à effectuer les fonctions et être en mesure de les utiliser. Avec Monad nous pouvons faire deux choses: nous pouvons traiter une pure valeur comme une valeur effectuant côté (return), et nous pouvons appliquer une fonction effectuant côté à une valeur effectuant côté pour obtenir une nouvelle valeur effectuant côté (>>=) . Perdre la capacité de faire l'une de ces choses serait rédhibitoire, notre type côte Effecting doit être « au moins » Monad, et il se trouve Monad suffit de mettre en œuvre tout ce que nous avons besoin de ce jour.

L'autre moitié est: quelle est la structure la plus détaillée que nous pourrions donner aux « effets secondaires possibles »? On peut certainement penser à l'espace de tous les effets secondaires possibles comme un ensemble (la seule opération qui nécessite l'adhésion est). Nous pouvons combiner deux effets secondaires en les faisant un après l'autre, et cela donnera lieu à un effet secondaire différent (ou peut-être le même - si le premier était « l'ordinateur d'arrêt » et le second était « écrire le fichier », le résultat de composer ces derniers est juste "ordinateur shutdown").

Ok, donc ce que nous pouvons dire au sujet de cette opération? Il est associative; qui est, si l'on combine trois effets secondaires, il n'a pas d'importance pour nous la combinaison. Si nous (fichier d'écriture puis lecture socket) puis éteindre l'ordinateur, il est la même chose que faire le fichier d'écriture puis (lire la prise puis l'arrêt ordinateur). Mais ce n'est pas commutative: ( « écrire le fichier » puis « supprimer le fichier ») est un effet secondaire différent de ( « supprimer le fichier » puis « fichier d'écriture »). Et nous avons une identité: « Groupe » l'effet secondaire spécial « aucun effet secondaire » fonctionne ( « pas d'effets secondaires », puis « supprimer le fichier » est le même effet secondaire que juste « supprimer le fichier ») A ce stade, tout mathématicien pense Mais les groupes ont Inverses et il n'y a aucun moyen d'inverser un effet secondaire en général; « Supprimer le fichier » est irréversible. Ainsi, la structure que nous avons laissé est celui d'un monoïde, ce qui signifie que nos fonctions latérales effectuer doivent être monades.

Y at-il une structure plus complexe? Sûr! On pourrait diviser les effets secondaires possibles dans les effets basés sur systèmes de fichiers, les effets basés sur le réseau et plus, et nous pourrions trouver des règles de composition plus élaborées qui préservaient ces détails. Mais encore une fois, il se résume à: Monad est très simple, et assez puissant pour exprimer la plupart des propriétés qui nous intéressent. (En particulier, associativité et les autres axiomes nous permettent de tester notre application en petits morceaux, avec confiance que les effets secondaires de l'application combinée sera le même que la combinaison des effets secondaires des pièces).

Il est en fait une façon tout à fait propre à penser E / S de manière fonctionnelle.

Dans la plupart des langages de programmation, vous faites des opérations d'entrée / sortie. En Haskell, imaginez le code d'écriture ne pas faire les opérations, mais pour générer une liste des opérations que vous souhaitez faire.

Monades sont la syntaxe juste assez exactement pour cela.

Si vous voulez savoir pourquoi monades par opposition à quelque chose d'autre, je pense que la réponse est qu'ils sont la meilleure façon fonctionnelle pour représenter I / O que les gens pourraient penser quand ils faisaient Haskell.

AFAIK, la raison est d'être en mesure d'inclure des contrôles d'effets secondaires dans le système de type. Si vous voulez en savoir plus, écouter les épisodes de ceux href="http://se-radio.net/" SE-Radio: Episode 108: Simon Peyton Jones sur la programmation fonctionnelle et Haskell Episode 72: Erik Meijer sur LINQ

Au-dessus, il y a de très bonnes réponses détaillées avec un fond théorique. Mais je veux donner mon avis sur IO monade. Je ne suis pas programmeur expérimenté haskell, alors peut-être il est assez naïf ou même faux. Mais je m'a aidé à faire face à IO monade dans une certaine mesure (note, qu'il ne se rapporte pas à d'autres monades).

D'abord, je veux dire, cet exemple avec « monde réel » n'est pas trop clair pour moi que nous ne pouvons pas accéder à ses (monde réel) états précédents. Peut-être qu'il ne concerne pas les calculs monade du tout, mais il est souhaitable dans le sens de la transparence référentielle, qui est présente généralement dans le code haskell.

Nous voulons que notre langue (haskell) d'être pur. Mais nous avons besoin d'entrées / sorties car sans eux notre programme ne peut pas être utile. Et ces opérations ne peuvent pas être pur par leur nature. Donc, la seule façon de faire face à cela, nous devons séparer les opérations impures du reste du code.

Voici monade vient. En fait, je ne suis pas sûr, qu'il ne peut exister d'autres constructions ayant des propriétés similaires nécessaires, mais le point est que monade ont ces propriétés, il peut donc être utilisé (et il est utilisé avec succès). La principale propriété est que nous ne pouvons pas y échapper. Interface monade ne pas les opérations pour se débarrasser de la monade autour de notre valeur. D'autres fournissent (non IO) monades ces opérations et permettent une adaptation motif (par exemple peut-être), mais ces opérations ne sont pas dans l'interface monade. Une autre propriété requise est la capacité d'opérations de la chaîne.

Si nous pensons à ce que nous avons besoin en termes de système de type, nous arrivons au fait que nous avons besoin de type avec le constructeur, qui peut être enroulé autour de tout vale. Constructor doit être privé, comme nous interdisons y échapper (i.e.. Pattern matching). Mais nous avons besoin de fonctionner à la valeur mettre dans ce constructeur (ici le retour vient à l'esprit). Et nous avons besoin de la voie aux opérations de la chaîne. Si on y réfléchit pendant un certain temps, nous arriverons au fait, cette opération doit être de type enchaînant que >> = a. Donc, nous arrivons à quelque chose de très semblable à monade. Je pense que, nous en viendrons à axiomes monade si nous analysons maintenant des situations contradictoires possibles avec cette construction,.

Note, qui a développé construction n'ont rien en commun avec l'impureté. Il a seulement des propriétés, que nous voulions avoir à être en mesure de traiter les opérations impurs, à savoir, non-escaping, enchaînant, et un moyen d'entrer.

Aujourd'hui, certains ensemble d'opérations impurs est prédéfinie par la langue sélectionnée dans ce monade IO. Nous pouvons combiner ces opérations pour créer de nouvelles opérations impure. Et toutes ces opérations devront avoir IO dans leur type. A noter cependant, que la présence de IO dans le type d'une fonction ne rend pas cette fonction impure. Mais si je comprends bien, il est mauvaise idée d'écrire des fonctions pures avec IO dans leur type, comme il était d'abord notre idée de séparer les fonctions pures et impures.

Enfin, je veux dire que monade ne se transforme pas en opérations impures les purs. Il permet seulement de les séparer efficacement. (Je le répète, que seule ma compréhension)

scroll top