2 votes

Curieux de savoir pourquoi get dans le transformateur de la monade StateT renvoie a au lieu de (a, s)

Je lisais le Source du transformateur de monades StateT Il s'agit d'un texte qui se lit comme suit

get :: (Monad m) => StateT s m s
get = state $ \ s -> (s, s)

J'ai élargi le code ci-dessus en remplaçant state et j'ai obtenu ceci, mais je ne comprends toujours pas pourquoi il ne renvoie pas un tuple.

a <- StateT (return . (\ s -> (s, s)))

D'après le code ci-dessus, il semble que get renvoie un tuple (s, s) Je me demande pourquoi, alors que je l'utilisais, il n'y avait pas de problème, get a renvoyé un Int au lieu de (Int, Int) ?

J'ai retracé une grande partie du code source en essayant de trouver quand ou ce qui l'a modifié, mais en vain.

w :: StateT Int IO String
w = do
  a <- get
  liftIO $ print a -- 2, but why? shouldn't this be (2, 2) instead?
  return $ show a

result = runStateT w 2 -- ("2",2)

3voto

chi Points 8104

Une valeur de type StateT s m a , modulo newtype est une fonction de type s -> m (a, s) .

La fonction (return . (\ s -> (s, s))) a le type s -> m (s, s) Ainsi, une fois qu'elle est enveloppée par le StateT devient une valeur de type StateT s m s .

Notez qu'une valeur de type StateT s m (s, s) impliquerait plutôt une fonction de type s -> m (s, (s, s)) ce qui n'est pas le cas ici.

Votre confusion semble provenir de l'"autre" s en m (s, s) qui ne contribue pas à la x lorsque nous courons x <- get . Pour comprendre pourquoi, il est utile de réfléchir à ce qu'un calcul avec état effectue :

  • Tout d'abord, nous lisons l'ancien état du type s . Il s'agit de la s -> .. dans le type s -> m (a, s) .
  • Ensuite, nous exécutons une action dans la monade m . Il s'agit de la .. -> m .. dans le type s -> m (a, s) .
    • L'action monadique renvoie un nouvel état qui remplace l'ancien. Il s'agit de l'action .. -> .. (.., s) dans le type s -> m (a, s) .
    • Enfin, l'action monadique renvoie également une valeur, d'un type éventuellement différent a . Ce projet .. -> .. (a, ..) dans le type s -> m (a, s) .

La course à pied x <- action gère automatiquement tous ces types de données pour nous, et permet à la x pour avoir le type de résultat a , uniquement.

Concrètement, considérons ce pseudo-code impératif :

global n: int

def foo():
   if n > 5:
      print ">5"
      n = 8
      return "hello"
   else:
      print "not >5"
      n = 10
      return "greetings"

Dans un langage impératif, nous taperions ceci sous la forme suivante foo(): string puisqu'elle renvoie une chaîne de caractères, sans tenir compte de ses effets secondaires sur le système global de gestion de l'information. n: int et les messages imprimés.

En Haskell, nous modéliserions plutôt cela en utilisant un type plus précis comme

Int -> IO (String, Int)
^-- the old n
       ^-- the printed stuff
           ^-- the returned string
                   ^-- the new n

Une fois de plus, l'exécution x <- foo() nous voulons x: string , pas x: (string, int) , à l'instar de ce qui se passerait dans un langage impératif.

Si, à la place, nous avions une fonction

global n: int

def bar():
   old_n = n
   n = n + 5
   return old_n

nous utiliserions le type

Int -> IO (Int, Int)

puisque la valeur retournée est un Int maintenant. De même,

global n: int

def get():
   return n

pourrait utiliser le même type

Int -> IO (Int, Int)

On pourrait arguer que la deuxième Int n'est pas strictement nécessaire ici, puisque get() n'est pas vraiment en train de produire un nouvel état - ce n'est pas changeant la valeur de n . Cependant, il est pratique d'utiliser un type de la même forme, s -> m (a, s) comme toute fonction qui podría changer l'état. Cela permet de l'utiliser avec n'importe quelle autre fonction de même type.

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