46 votes

Quels autres moyens de l'état d'être traitées dans un langage purement fonctionnel en plus avec les Monades?

J'ai donc commencé à envelopper ma tête autour de Monades (utilisé en Haskell). Je suis curieux de ce que d'autres moyens IO ou de l'état peuvent être traitées dans un langage purement fonctionnel (à la fois dans la théorie ou de la réalité). Par exemple, il y a une logique de la langue appelée le "mercure", qui utilise l'effet du "groupage". Dans un programme comme haskell, comment effet-travaux de dactylographie? Comment d'autres systèmes de travail?

79voto

shachaf Points 6455

Il y a plusieurs questions en cause ici.

Tout d'abord, IO et State sont des choses très différentes. State est facile à faire vous-même: il suffit de passer un argument supplémentaire pour chaque fonction et retourner un supplément résultat, et vous avez une "dynamique de la fonction"; par exemple, tourner a -> ben a -> s -> (b,s).

Il n'y a pas quelque chose de magique ici: Control.Monad.State fournit un wrapper qui rend le travail avec les "actions de l'etat" de la forme s -> (a,s) pratique, ainsi comme un tas de fonctions d'aide, mais c'est tout.

I/O, de par sa nature, doit avoir un peu de magie dans sa mise en œuvre. Mais il y a beaucoup de façons d'exprimer des I/O en Haskell qui ne comportent pas le mot "monade". Si nous avions un IO-gratuit sous-ensemble de Haskell comme-est, et nous avons voulu inventer IO de zéro, sans rien connaître de monades, il y a beaucoup de choses qu'on peut n'.

Par exemple, si tout ce que nous voulons faire est d'imprimer sur la sortie standard, on pourrait dire:

type PrintOnlyIO = String

main :: PrintOnlyIO
main = "Hello world!"

Et puis avoir un RTS (runtime) qui évalue la chaîne et l'imprime. Ceci nous permet d'écrire tout Haskell programme dont les I/O se compose entièrement d'impression à stdout.

Ce n'est pas très utile, cependant, parce que nous voulons que l'interactivité! Donc, nous allons inventer un nouveau type de IO qui permet. La chose la plus simple qui vient à l'esprit est

type InteractIO = String -> String

main :: InteractIO
main = map toUpper

Cette approche IO nous permet d'écrire du code qui lit sur l'entrée standard et écrit à stdout (le Prélude est livré avec une fonction interact :: InteractIO -> IO () qui le fait, par la voie).

C'est beaucoup mieux, car il nous permet d'écrire des programmes interactifs. Mais c'est encore très limitée par rapport à tous les IO que nous voulons faire, et également tout à fait sujettes à l'erreur (si nous avons accidentellement essayer de lire trop loin dans stdin, le programme va juste bloquer jusqu'à ce que l'utilisateur tape plus dans).

Nous voulons être en mesure de faire plus que de lire l'entrée standard stdin et écrire sur la sortie standard. Voici comment les premières versions de Haskell n'ai-je/O, environ:

data Request = PutStrLn String | GetLine | Exit | ...
data Response = Success | Str String | ...
type DialogueIO = [Response] -> [Request]

main :: DialogueIO
main resps1 =
    PutStrLn "what's your name?"
  : GetLine
  : case resps1 of
        Success : Str name : resps2 ->
            PutStrLn ("hi " ++ name ++ "!")
          : Exit

Lorsque nous écrivons, main, nous obtenons un paresseux liste des arguments et retourne un paresseux liste résultat. Le paresseux liste nous de retour a des valeurs comme l' PutStrLn s et GetLine; après nous donner une (demande) valeur, nous pouvons examiner l'élément suivant de la (réponse) de la liste, et la RTS sera nécessaire pour qu'elle soit la réponse à notre demande.

Il y a des façons de faire le travail avec ce mécanisme de plus belle, mais comme vous pouvez le imaginez, l'approche est assez maladroit assez rapidement. Aussi, c'est le risque d'erreur de la même façon que la précédente.

Voici une autre approche qui est beaucoup moins sujette à erreur, et conceptuellement très à la façon dont Haskell IO se comporte effectivement:

data ContIO = Exit | PutStrLn String ContIO | GetLine (String -> ContIO) | ...

main :: ContIO
main =
    PutStrLn "what's your name?" $
    GetLine $ \name ->
    PutStrLn ("hi " ++ name ++ "!") $
    Exit

La clé, c'est qu'au lieu de prendre un "paresseux" liste de réponses comme une grande argument au début de la main, nous faisons des demandes individuelles qui acceptent un argument à la fois.

Notre programme est maintenant juste un type de données -- un peu comme une liste liée, à l'exception de vous ne pouvez pas simplement le traverser normalement: Lors de la RTS interprète main, parfois il rencontre une valeur comme GetLine qui détient une fonction; il doit alors obtenir une chaîne de caractères à partir de stdin à l'aide de la RTS de la magie, et de transmettre cette chaîne à la fonction, avant de pouvoir continuer. Exercice: Écrivez - interpret :: ContIO -> IO ().

Notez qu'aucune de ces implémentations impliquer "dans le monde, en passant". "le monde de passage" n'est pas vraiment la façon dont I/O fonctionne en Haskell. Le réel la mise en œuvre de l' IO type dans GHC implique un type interne appelé RealWorld, mais ce n'est qu'un détail d'implémentation.

Réelle Haskell IO ajoute un paramètre de type, donc on peut écrire les actions qui "produire" des valeurs arbitraires -- de sorte qu'il ressemble plus à de la data IO a = Done a | PutStr String (IO a) | GetLine (String -> IO a) | .... Cela nous donne plus de flexibilité, car il peut créer de "IO d'actions" qui produisent de l'arbitraire des valeurs.

(Russell O'Connor souligne, ce type est juste un gratuit monade. Nous pouvons écrire une Monad exemple pour facilement.)


Où puis-monades venir en elle, alors? Il s'avère que nous n'avons pas besoin Monadpour I/O, et nous n'avons pas besoin d' Monad pour l'état, alors pourquoi avons-nous besoin? L' la réponse est que nous n'avons pas. Il n'y a rien de magique à propos de la classe de type Monad.

Cependant, lorsque nous travaillons avec des IO et State (et les listes de fonctions et de Maybe et d'analyseurs et de continuation-passant du style et de la ...) pour assez longtemps, nous finalement comprendre qu'ils se comportent de façon assez similaire à certains égards. Nous pourrions écrire une fonction qui imprime chaque chaîne de caractères dans une liste, et une fonction qui s'exécute chaque stateful de calcul dans une liste et les fils de l'état à travers, et ils vont look très semblables les uns aux autres.

Puisque nous ne sommes pas comme la rédaction d'un lot de même l'avenir de code, nous avons besoin d'un moyen pour résumé il; Monad s'avère être d'une grande abstraction, parce qu'il nous permet de résumé de nombreux types qui semblent très différentes, mais encore fournir beaucoup d'informations utiles fonctionnalité (tout compris, en Control.Monad).

Compte tenu de bindIO :: IO a -> (a -> IO b) -> IO b et returnIO :: a -> IO a, nous pourrais écrire tout IO programme en Haskell, sans jamais penser à les monades. Mais nous serions probablement jusqu'à la fin de la réplication de beaucoup de fonctions en Control.Monad, comme mapM et forever et when et (>=>).

Par la mise en œuvre de la commune, Monad API, nous apprenons à utiliser le même code pour travailler avec IO actions comme nous le faisons avec les analyseurs et les listes. C'est vraiment le seul raison pour laquelle nous avons l' Monad classe -- pour capturer les similitudes entre différents types.

21voto

isturdy Points 1173

Une autre approche majeure est l'unicité de frappe, comme en Propre. La petite histoire c'est que les poignées de l'état (y compris le monde réel) ne peut être utilisé qu'une fois, et les fonctions que l'accès mutable état de retour une nouvelle poignée. Cela signifie que la sortie du premier appel est une entrée de seconde, de forcer l'ordre d'évaluation.

Effet saisissant est utilisé dans le Disciple Compilateur pour Haskell, mais à ma connaissance, il serait beaucoup compilateur de travail pour lui permettre, par exemple, de GHC. Je laisse la discussion des détails à ceux qui sont mieux informés que moi.

9voto

ertes Points 81

Bien, d'abord, qu'est-ce que l'état? Elle peut se manifester comme une variable mutable, qui vous n'avez pas en Haskell. Vous avez seulement les références de mémoire (IORef, MVar, Ptr, etc.) et IO/ST actions pour agir sur eux.

Cependant, l'état lui-même peut être pur bien. De reconnaître que l'examen de la "Stream" de type:

data Stream a = Stream a (Stream a)

C'est un flux de valeurs. Cependant une autre façon d'interpréter ce type est une évolution de la valeur:

stepStream :: Stream a -> (a, Stream a)
stepStream (Stream x xs) = (x, xs)

Cela devient intéressant lorsque vous autorisez les deux flux de communiquer. Vous obtenez alors l'automate catégorie Automatique:

newtype Auto a b = Auto (a -> (b, Auto a b))

C'est vraiment comme Stream, sauf que maintenant, à chaque instant le flux obtient une valeur d'entrée de type un. Cela forme une catégorie, alors un instant d'un cours d'eau peut prendre sa valeur à l'instant d'un autre flux.

Encore une interprétation différente de ceci: Vous avez deux calculs qui changent au fil du temps et vous permettent de communiquer. Ainsi, chaque calcul est locales de l'état. Voici un type qui est isomorphe a' Auto:

data LS a b =
    forall s.
    LS s ((a, s) -> (b, s))

8voto

Jan Stolarek Points 567

Prendre un coup d'oeil à l'Histoire de Haskell: Être Paresseux Avec Classe. Il décrit deux approches différentes pour faire des I/O en Haskell, avant de monades ont été inventés: suites et des ruisseaux.

4voto

thSoft Points 5513

Il s'agit d'une approche Fonctionnelle Réactive de Programmation qui représente variant dans le temps, de valeurs et/ou des flux d'événements de première classe de l'abstraction. Un exemple récent qui me vient à l'esprit est de l'Orme qui est écrit en Haskell et a une syntaxe similaire à Haskell.

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