La monade du lecteur est si complexe et semble inutile. Dans un langage impératif comme Java ou C ++, il n'y a pas de concept équivalent pour la monade du lecteur, si je ne me trompe pas.
Pouvez-vous me donner un exemple simple et éclaircir un peu cela?
haskell
monads
reader-monad
chipbk10
la source
la source
Réponses:
N'aie pas peur! La monade de lecture n'est en fait pas si compliquée et possède une réelle utilité facile à utiliser.
Il y a deux manières d'aborder une monade: on peut demander
Dès la première approche, la monade du lecteur est un type abstrait
tel que
Alors, comment utilisons-nous cela? Eh bien, la monade de lecture est bonne pour passer des informations de configuration (implicites) à travers un calcul.
Chaque fois que vous avez une "constante" dans un calcul dont vous avez besoin à différents moments, mais que vous souhaitez vraiment pouvoir effectuer le même calcul avec des valeurs différentes, vous devez utiliser une monade de lecture.
Les monades de lecteur sont également utilisées pour faire ce que les personnes OO appellent l' injection de dépendances . Par exemple, l' algorithme negamax est fréquemment utilisé (sous des formes hautement optimisées) pour calculer la valeur d'une position dans une partie à deux joueurs. L'algorithme lui-même ne se soucie pas du jeu auquel vous jouez, sauf que vous devez être en mesure de déterminer quelles sont les "prochaines" positions dans le jeu, et vous devez être capable de dire si la position actuelle est une position de victoire.
Cela fonctionnera ensuite avec n'importe quelle partie à deux joueurs finie et déterministe.
Ce modèle est utile même pour les choses qui ne sont pas vraiment une injection de dépendance. Supposons que vous travaillez dans la finance, vous pouvez concevoir une logique compliquée pour évaluer un actif (un dérivé, par exemple), ce qui est très bien et vous pouvez vous passer de monades puantes. Mais ensuite, vous modifiez votre programme pour traiter plusieurs devises. Vous devez être capable de convertir entre les devises à la volée. Votre première tentative consiste à définir une fonction de niveau supérieur
pour obtenir des prix au comptant. Vous pouvez alors appeler ce dictionnaire dans votre code .... mais attendez! Cela ne fonctionnera pas! Le dictionnaire de devises est immuable et doit donc être le même non seulement pour la durée de vie de votre programme, mais aussi à partir du moment où il est compilé ! Donc que fais-tu? Eh bien, une option serait d'utiliser la monade Reader:
Le cas d'utilisation le plus classique est peut-être celui de l'implémentation d'interprètes. Mais, avant de regarder cela, nous devons introduire une autre fonction
D'accord, donc Haskell et d'autres langages fonctionnels sont basés sur le calcul lambda . Le calcul Lambda a une syntaxe qui ressemble à
et nous voulons écrire un évaluateur pour cette langue. Pour ce faire, nous devrons garder une trace d'un environnement, qui est une liste de liaisons associées à des termes (en fait, ce seront des fermetures parce que nous voulons faire une portée statique).
Lorsque nous avons terminé, nous devrions sortir une valeur (ou une erreur):
Alors, écrivons l'interpréteur:
Enfin, nous pouvons l'utiliser en passant un environnement trivial:
Et c'est tout. Un interpréteur entièrement fonctionnel pour le calcul lambda.
L'autre façon d'y penser est de se demander: comment est-il mis en œuvre? La réponse est que la monade du lecteur est en fait l'une des monades les plus simples et les plus élégantes.
Reader est juste un nom sophistiqué pour les fonctions! Nous avons déjà défini
runReader
alors qu'en est-il des autres parties de l'API? Eh bien, toutMonad
est aussi unFunctor
:Maintenant, pour obtenir une monade:
ce qui n'est pas si effrayant.
ask
c'est vraiment simple:alors que ce
local
n'est pas si mal:D'accord, donc la monade du lecteur n'est qu'une fonction. Pourquoi avoir Reader du tout? Bonne question. En fait, vous n'en avez pas besoin!
Celles-ci sont encore plus simples. De plus,
ask
c'est justeid
etlocal
c'est juste une composition de fonction avec l'ordre des fonctions commuté!la source
Reader
une fonction avec une implémentation particulière de la classe de type monade? Le dire plus tôt m'aurait aidé à être un peu moins perplexe. D'abord, je ne comprenais pas. A mi-chemin, j'ai pensé "Oh, cela vous permet de retourner quelque chose qui vous donnera le résultat souhaité une fois que vous aurez fourni la valeur manquante." J'ai pensé que c'était utile, mais j'ai soudainement réalisé qu'une fonction faisait exactement cela.local
fonction a cependant besoin de plus d'explications.(Reader f) >>= g = (g (f x))
?x
?Je me souviens avoir été perplexe comme vous l'étiez, jusqu'à ce que je découvre par moi-même que des variantes de la monade Reader sont partout . Comment l'ai-je découvert? Parce que j'ai continué à écrire du code qui s'est avéré être de petites variations.
Par exemple, à un moment donné, j'écrivais du code pour traiter des valeurs historiques ; des valeurs qui changent avec le temps. Un modèle très simple de ceci est des fonctions de points de temps à la valeur à ce moment-là:
L'
Applicative
instance signifie que si vous avezemployees :: History Day [Person]
et quecustomers :: History Day [Person]
vous pouvez le faire:C'est-à-dire,
Functor
etApplicative
nous permettent d'adapter des fonctions régulières et non historiques pour travailler avec des histoires.L'instance de monade est plus intuitivement comprise en considérant la fonction
(>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
. Une fonction de typea -> History t b
est une fonction qui mappe una
à une histoire deb
valeurs; par exemple, vous pourriez avoirgetSupervisor :: Person -> History Day Supervisor
, etgetVP :: Supervisor -> History Day VP
. Ainsi, l'instance Monad pourHistory
est de composer des fonctions comme celles-ci; par exemple,getSupervisor >=> getVP :: Person -> History Day VP
est la fonction qui obtient, pour toutPerson
, l'historique desVP
s qu'ils ont eu.Eh bien, cette
History
monade est en fait exactement la même queReader
.History t a
est vraiment le même queReader t a
(qui est le même quet -> a
).Un autre exemple: j'ai récemment prototypé des conceptions OLAP dans Haskell. Une idée ici est celle d'un «hypercube», qui est une correspondance entre les intersections d'un ensemble de dimensions et les valeurs. On y va encore une fois:
Une opération courante sur les hypercubes est d'appliquer des fonctions scalaires multi-places aux points correspondants d'un hypercube. Nous pouvons l'obtenir en définissant une
Applicative
instance pourHypercube
:Je viens de copier le
History
code ci-dessus et j'ai changé de nom. Comme vous pouvez le voir,Hypercube
c'est aussi justeReader
.Et ça continue, encore et encore. Par exemple, les interprètes linguistiques se résument également à
Reader
, lorsque vous appliquez ce modèle:Reader
ask
Reader
environnement d'exécution.local
Une bonne analogie est que a
Reader r a
représente una
avec des «trous», qui vous empêchent de savoir de quoia
nous parlons. Vous ne pouvez obtenir un réela
qu'une fois que vous avez fourni un anr
pour remplir les trous. Il y a des tonnes de choses comme ça. Dans les exemples ci-dessus, un «historique» est une valeur qui ne peut pas être calculée tant que vous n’avez pas spécifié une heure, un hypercube est une valeur qui ne peut pas être calculée tant que vous n’avez pas spécifié une intersection, et une expression de langage est une valeur qui peut ne sera pas calculé tant que vous ne fournissez pas les valeurs des variables. Cela vous donne également une intuition sur pourquoiReader r a
est la même chose quer -> a
, car une telle fonction est également intuitivement una
fichierr
.Ainsi
Functor
, les instances ,Applicative
etMonad
deReader
sont une généralisation très utile pour les cas où vous modélisez quelque chose du genre «eta
qui manque unr
», et vous permettent de traiter ces objets «incomplets» comme s'ils étaient complets.Encore une autre façon de dire la même chose: a
Reader r a
est quelque chose qui consommer
et produita
, et les instancesFunctor
,Applicative
etMonad
sont des modèles de base pour travailler avecReader
s.Functor
= faire unReader
qui modifie la sortie d'un autreReader
;Applicative
= connecter deuxReader
s à la même entrée et combiner leurs sorties;Monad
= inspecter le résultat de aReader
et l'utiliser pour en construire un autreReader
. Les fonctionslocal
etwithReader
= font unReader
qui modifie l'entrée en une autreReader
.la source
GeneralizedNewtypeDeriving
extension DeriveFunctor
,Applicative
,Monad
, etc. pour newtypes en fonction de leurs types fondamentaux.En Java ou C ++, vous pouvez accéder à n'importe quelle variable de n'importe où sans aucun problème. Des problèmes apparaissent lorsque votre code devient multithread.
Dans Haskell, vous n'avez que deux façons de passer la valeur d'une fonction à une autre:
fn1 -> fn2 -> fn3
fonctionfn2
peut ne pas avoir besoin du paramètre que vous passez defn1
àfn3
.Le Reader monad transmet simplement les données que vous souhaitez partager entre les fonctions. Les fonctions peuvent lire ces données, mais ne peuvent pas les modifier. C'est tout ce que fait la monade Reader. Enfin, presque tous. Il existe également un certain nombre de fonctions comme
local
, mais pour la première fois, vous ne pouvez vous en tenirasks
qu'à.la source
do
-notation, ce qui serait mieux d'être refactorisé en une fonction pure.where
clause, sera-t-elle acceptée comme troisième moyen de passer des variables?