40 votes

Est le monadique IO construire en Haskell qu'une convention?

Est le monadique IO construire en Haskell qu'une convention ou est-il est une mise en œuvre de raison?

Ne pourriez-vous pas juste FFI dans la libc.ainsi, au lieu de faire vos IO, et de sauter la IO Monade pieceg?

Serait-il travailler de toute façon, ou est le résultat undeterministic en raison de Haskell évaluation des paresseux ou quelque chose d'autre, comme le GHC est la correspondance de modèle pour IO Monade et ensuite de le manipuler d'une manière spéciale ou quelque chose d'autre.

Quelle est la vraie raison? En fin de compte vous vous retrouvez dans un des effets secondaires. Alors pourquoi ne pas le faire de la manière la plus simple?

70voto

Jake King Points 6308

Oui, monadique I/O est une conséquence de Haskell être paresseux. Plus précisément, si, monadique I/O est une conséquence de Haskell être pur, qui est effectivement nécessaire pour un paresseux langue prévisible.

C'est facile à illustrer avec un exemple. Imaginez un instant que Haskell étaient pas pure, mais il était toujours paresseux. Au lieu de putStrLn ayant le type String -> IO (), il aurait tout simplement le type String -> (), et il affiche une chaîne de caractères sur la sortie standard stdout comme un effet secondaire. Le problème avec ceci est que cela ne se produit que lors de l' putStrLn est en fait appelé, et dans un paresseux de la langue, les fonctions ne sont appelés lorsque leurs résultats sont nécessaires.

Voici le problème: putStrLn produit (). Recherche d'une valeur de type () est inutile, parce qu' () signifie "ennuyeux". Cela signifie que ce programme permettrait de faire ce que vous attendez:

main :: ()
main =
  case putStr "Hello, " of
    () -> putStrLn " world!"

-- prints "Hello, world!\n"

Mais je pense que vous pouvez convenir que le style de programmation est assez bizarre. L' case ... of est nécessaire, cependant, car les forces de l'évaluation de l'appel à putStr par correspondance contre (). Si vous tordre le programme légèrement:

main :: ()
main =
  case putStr "Hello, " of
    _ -> putStrLn " world!"

...maintenant, il imprime world!\n, et le premier appel n'est pas évaluée à tous.

Cette réalité est encore pire, mais, parce qu'il devient encore plus difficile de prédire dès que vous commencez à essayer de faire toute la programmation. Considérons ce programme:

printAndAdd :: String -> Integer -> Integer -> Integer
printAndAdd msg x y = putStrLn msg `seq` (x + y)

main :: ()
main =
  let x = printAndAdd "first" 1 2
      y = printAndAdd "second" 3 4
  in (y + x) `seq` ()

Ce programme ne fait imprimer first\nsecond\n ou second\nfirst\n? Sans connaître l'ordre dans lequel (+) évalue ses arguments, nous ne savons pas. Et en Haskell, l'ordre d'évaluation n'est même pas toujours bien définies, il est donc tout à fait possible que l'ordre dans lequel les deux effets sont exécutées est en fait totalement impossible à déterminer!

Ce problème ne se pose pas dans le strict langues avec une évaluation bien définis de l'ordre, mais dans un paresseux des langages comme Haskell, nous avons besoin d'une structure supplémentaire pour assurer les effets secondaires sont (a) évalués et (b) exécutés dans l'ordre correct. Les monades se trouvent être une interface avec élégance que de fournir la structure nécessaire pour faire respecter cet ordre.

Pourquoi est-ce? Et comment est-ce même possible? Eh bien, la monadique de l'interface fournit une notion de dépendance de données dans la signature d' >>=, ce qui impose une évaluation bien définis afin. Haskell mise en œuvre de l' IO est "magique", dans le sens où il est mis en œuvre dans l'exécution, mais le choix de la monadique de l'interface est loin d'être arbitraire. Il semble être une assez bonne façon de coder la notion d'actions séquentielles dans une langue pure, et il le rend possible pour Haskell, pour être paresseux et de referentially transparent sans sacrifier prévisible de séquençage d'effets.

Il est intéressant de noter que les monades ne sont pas la seule façon d'encoder les effets secondaires d'une façon pure-en fait, historiquement, ils ne sont même pas la seule façon Haskell traitées effets secondaires. Ne pas être induit en erreur en pensant que les monades sont uniquement pour les I/O (ils ne le sont pas), utile uniquement dans un paresseux de la langue (ils sont beaucoup utile pour maintenir la pureté même dans un strict de la langue), utile uniquement dans une langue pure (beaucoup de choses sont utiles monades qui ne sont pas seulement pour l'application de la pureté), ou que vous avez besoin de monades pour faire des I/O (vous n'avez pas). Ils ne semblent avoir bien fonctionné assez bien en Haskell à ces fins, si.


† À ce sujet, Simon Peyton Jones a noté que "la Paresse vous tient honnête" à l'égard de la pureté.

25voto

Michal Charemza Points 6269

Pourriez-vous FFI dans la libc.ainsi, au lieu de faire de IO, et de sauter la IO Monade chose?

En prenant de l' https://en.wikibooks.org/wiki/Haskell/FFI#Impure_C_Functionssi vous déclarez un FFI de la fonction en tant que pure (donc, sans référence aux IO), puis

GHC ne voit pas de point dans le calcul de deux fois le résultat d'une fonction pure

ce qui signifie que le résultat de l'appel de la fonction est effectivement mis en cache. Par exemple, un programme où un étranger impur pseudo-random number generator est déclarée pour retourner un CUInt

{-# LANGUAGE ForeignFunctionInterface #-}

import Foreign
import Foreign.C.Types

foreign import ccall unsafe "stdlib.h rand"
  c_rand :: CUInt

main = putStrLn (show c_rand) >> putStrLn (show c_rand)

retourne la même chose à chaque appel, au moins sur mon compilateur/système:

16807
16807

Si nous changeons la déclaration de retourner un IO CUInt

{-# LANGUAGE ForeignFunctionInterface #-}

import Foreign
import Foreign.C.Types

foreign import ccall unsafe "stdlib.h rand"
  c_rand :: IO CUInt

main = c_rand >>= putStrLn . show >> c_rand >>= putStrLn . show

alors cela donne (sans doute) un nombre différent retourné chaque appel, puisque le compilateur sait qu'il est impur:

16807
282475249

Si vous êtes de retour à avoir à utiliser IO pour les appels vers les bibliothèques standard de toute façon.

12voto

luqui Points 26009

Disons que l'aide de l'IFF, nous avons défini une fonction

c_write :: String -> ()

qui se trouve sur sa pureté, en ce qu'à chaque fois que son résultat est forcé, il affiche la chaîne de caractères. De sorte que nous ne courez pas dans les problèmes de mise en cache dans Michal réponse, nous pouvons définir ces fonctions pour prendre un extra - () argument.

c_write :: String -> () -> ()
c_rand :: () -> CUInt

Sur un niveau de mise en œuvre de ce travailler aussi longtemps que le CST n'est pas trop agressif (ce qui n'est pas dans GHC parce que cela peut conduire à des fuites de mémoire, il s'avère). Maintenant que nous avons des choses définies de cette façon, il y a beaucoup de maladroit des questions d'utilisation, Alexis points, mais nous pouvons les résoudre à l'aide d'une monade:

newtype IO a = IO { runIO :: () -> a }

instance Monad IO where
    return = IO . const
    m >>= f = IO $ \() -> let x = runIO m () in x `seq` f x

rand :: IO CUInt
rand = IO c_rand

Fondamentalement, nous avons juste des trucs tout d'Alexis est maladroit des questions d'utilisation dans une monade, et aussi longtemps que nous utilisons le monadique de l'interface, tout reste prévisible. En ce sens, IO n'est qu'une convention—parce que nous pouvons mettre en œuvre dans Haskell il n'y a rien de fondamental sur elle.

C'est à partir du point de vue opérationnel.

D'autre part, Haskell de la sémantique dans le rapport sont spécifiées à l'aide de denotational sémantique seul. Et, à mon avis, le fait que Haskell a un denotational la sémantique est l'un des plus beaux et des qualités utiles de la langue, ce qui me permet un cadre précis, à penser à des abstractions et donc de gérer la complexité avec précision. Et tandis que l'habitude abstrait IO monade n'a pas accepté denotational sémantique (la complainte de certains d'entre nous), c'est au moins concevable que l'on pouvait créer un denotational modèle, permettant ainsi de conserver certains des avantages de Haskell denotational modèle. Toutefois, la forme de I/O, nous avons juste donné est complètement incompatible avec Haskell denotational sémantique.

Tout simplement, il y a seulement censé être distinguer deux valeurs (modulo fatale messages d'erreur) de type (): () et ⊥. Si nous traitons les FFI que les fondamentaux de l'I/O et l'utilisation de l' IO monade seulement "la convention", puis nous avons effectivement ajouter un jillion valeurs de chaque type pour continuer à avoir un denotational sémantique, chaque valeur doit être accolé avec la possibilité d'effectuer des e/S avant son évaluation, et, avec le supplément de la complexité de cette présente, nous avons essentiellement perdre tout notre capacité à tenir compte des deux programmes distincts équivalents, sauf dans la plupart des cas triviaux—qui est, nous perdons notre capacité à refactoriser.

Bien sûr, en raison de l' unsafePerformIO cela est techniquement déjà le cas, et avancé Haskell programmeurs avez besoin de penser à propos de la sémantique opérationnelle ainsi. Mais la plupart du temps, y compris lorsque l'on travaille avec des I/O, nous pouvons oublier tout cela et de refactoriser avec confiance, précisément parce que nous avons appris que lorsque nous utilisons unsafePerformIO, nous devons être très prudent afin de s'assurer qu'il joue bien, qu'il continue de nous offre autant de denotational raisonnement que possible. Si une fonction a unsafePerformIO,- je automatiquement donner 5 ou 10 fois plus d'attention que d'ordinaire les fonctions, parce que j'ai besoin de comprendre la validité des modèles d'utilisation (généralement la signature d'un type me dit tout ce que j'ai besoin de savoir), j'ai besoin de réfléchir sur la mise en cache et les conditions de course, j'ai besoin de réfléchir sur la façon profonde j'ai besoin de la force de ses résultats, etc. C'est affreux[1]. Le même soin serait nécessaire de FFI I/O.

En conclusion: oui, c'est une convention, mais si vous ne le suivez pas puis on ne peut pas avoir de belles choses.

[1] et Bien en fait je pense que c'est assez amusant, mais c'est sûrement pas pratique pour penser à toutes ces complexités tout le temps.

4voto

Rein Henrichs Points 3592

Cela dépend de ce que le sens de "est" est-ou au moins le sens de l'expression "convention" est.

Si une "convention" s'entend de "la façon dont les choses sont généralement fait" ou "un accord entre les parties, couvrant une question particulière", alors il est facile de donner un ennuyeux réponse: oui, l' IO monade est une convention. C'est la façon dont les concepteurs de la langue convenue pour gérer les opérations d'e / s et de la manière dont les usagers de la langue habituellement effectuer des opérations d'e / s.

Si nous sommes autorisés à choisir une offre plus intéressante définition de "convention" ensuite, nous pouvons obtenir une réponse intéressante. Si une "convention" est une discipline imposée sur une langue par ses utilisateurs afin d'atteindre un objectif particulier sans l'aide de la langue elle-même, alors la réponse est non: l' IO monade est le contraire d'une convention. C'est une discipline imposée par le langage qui accompagne ses utilisateurs dans la construction et le raisonnement sur les programmes.

Le but de l' IO type est de créer une distinction claire entre les types de "pur" les valeurs et les types de valeurs qui exigent l'exécution par le système d'exécution pour générer un résultat significatif. Le Haskell type de système applique cette stricte séparation, d'empêcher un utilisateur d' (dire) la création d'une valeur de type Int qui lance la proverbiale des missiles. Ce n'est pas une convention, dans le second sens: son but est de déplacer la discipline nécessaire pour effectuer des effets secondaires dans un coffre-fort et de manière cohérente de l'utilisateur et sur la langue et son compilateur.

Pourriez-vous FFI dans la libc.ainsi, au lieu de faire de IO, et de sauter la IO Monade chose?

Il est bien sûr possible de faire de la IO sans IO monade: voir presque tous les autres existants langage de programmation.

Serait-il travailler de toute façon, ou est le résultat undeterministic en raison de Haskell évaluation des paresseux ou quelque chose d'autre, comme le GHC est la correspondance de modèle pour IO Monade et ensuite de le manipuler d'une manière spéciale ou quelque chose d'autre.

Il n'y a pas une telle chose comme un repas gratuit. Si Haskell autorisée, toute valeur d'exiger l'exécution impliquant IO puis il aurait à perdre d'autres choses que nous apprécions. Le plus important est probablement la transparence référentielle: si myInt parfois 1 , et parfois 5 en fonction de facteurs externes, alors nous perdrions plus de notre capacité à raisonner au sujet de nos programmes de manière rigoureuse (connu sous le nom général paramétré par le raisonnement).

La paresse a été mentionné dans d'autres réponses, mais le problème avec la paresse seraient spécifiquement que le partage ne serait plus en sécurité. Si x en let x = someExpensiveComputationOf y in x * x n'a pas été referentially transparent, GHC ne serait pas en mesure de partager le travail et aurait pour calculer deux fois.

Quelle est la vraie raison?

Sans la stricte séparation de effectful valeurs de non-effectful valeurs fournies par IO et appliquées par le compilateur Haskell permettrait de cesser d'être Haskell. Il y a beaucoup de langues qui n'appliquent pas cette discipline. Il serait intéressant d'avoir au moins un autour de qui ne.

En fin de compte en fin de compte vous vous endup dans un sideeffect. Alors pourquoi ne pas le faire de la manière la plus simple?

Oui, à la fin de votre programme est représenté par une valeur appelée main avec un ar type. Mais la question n'est pas là où vous vous retrouvez, c'est l'endroit où vous commencez: Si vous commencez par être capable de différencier entre effectful et non effectful valeurs d'une manière rigoureuse, alors vous gagnez beaucoup d'avantages lors de la construction de ce programme.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X