48 votes

Transformateurs de monades et passage de paramètres aux fonctions

Je suis novice en Haskell mais je comprends comment les transformateurs de monades peuvent être utilisés. Pourtant, j'ai toujours des difficultés à saisir l'avantage qu'ils prétendent avoir sur le passage de paramètres aux appels de fonctions.

Basé sur le wiki Les transformateurs de monades expliqués nous avons essentiellement un objet de configuration défini comme suit

data Config = Config Foo Bar Baz

et de le faire circuler, au lieu d'écrire des fonctions avec cette signature

client_func :: Config -> IO ()

nous utilisons un Transformateur de Monade de ReaderT et changeons la signature en

client_func :: ReaderT Config IO ()

tirer la Config est alors juste un appel à ask .

L'appel de la fonction passe de client_func c à runReaderT client_func c

Bien.

Mais pourquoi cela rend-il mon application plus simple ?

1- Je soupçonne que les transformateurs de monades ont un intérêt lorsque vous assemblez un grand nombre de fonctions/modules pour former une application. Mais c'est là que s'arrête ma compréhension. Quelqu'un pourrait-il m'éclairer ?

2- Je n'ai pas trouvé de documentation sur la façon d'écrire une grande modulaire en Haskell, où les modules exposent une certaine forme d'API et cachent leurs implémentations, tout en cachant (partiellement) leurs propres états et environnements aux autres modules. Des pistes, s'il vous plaît ?

(Edit : Real World Haskell déclare que "... cette approche [Monad Transformers] ... s'adapte à de plus gros programmes", mais il n'y a pas d'exemple clair démontrant cette affirmation).

EDIT suivant Chris Taylor Réponse ci-dessous

Chris explique parfaitement pourquoi l'encapsulation de Config, State, etc... dans un Transformer Monad apporte deux avantages :

  1. Elle évite qu'une fonction de niveau supérieur doive maintenir dans sa signature de type tous les paramètres requis par les (sous-)fonctions qu'elle appelle mais qui ne sont pas nécessaires pour sa propre utilisation (voir la section getUserInput fonction)
  2. et, par conséquent, rend les fonctions de niveau supérieur plus résistantes à un changement du contenu de la monade transformateur (disons que vous voulez ajouter une fonction Writer pour fournir une journalisation dans une fonction de niveau inférieur)

Cela a pour conséquence de modifier la signature de toutes les fonctions afin qu'elles s'exécutent "dans" la monade transformatrice.

La question 1 est donc entièrement couverte. Merci Chris.

La réponse à la question 2 se trouve maintenant dans ce poste SO

48voto

Chris Taylor Points 25079

Disons que nous écrivons un programme qui a besoin de certaines informations de configuration sous la forme suivante :

data Config = C { logFile :: FileName }

Une façon d'écrire le programme est de faire passer explicitement la configuration entre les fonctions. Ce serait bien si nous devions seulement la passer aux fonctions qui l'utilisent explicitement, mais malheureusement nous ne sommes pas sûrs qu'une fonction puisse avoir besoin d'appeler une autre fonction qui utilise la configuration, donc nous sommes obligés de la passer en paramètre partout (en effet, ce sont généralement les fonctions de bas niveau qui ont besoin d'utiliser la configuration, ce qui nous oblige à la passer aussi à toutes les fonctions de haut niveau).

Écrivons le programme comme ça, puis nous le réécrirons en utilisant la fonction Reader et voir quel avantage nous en retirons.

Option 1. Passage explicite de la configuration

On se retrouve avec quelque chose comme ça :

readLog :: Config -> IO String
readLog (C logFile) = readFile logFile

writeLog :: Config -> String -> IO ()
writeLog (C logFile) message = do x <- readFile logFile
                                  writeFile logFile $ x ++ message

getUserInput :: Config -> IO String
getUserInput config = do input <- getLine
                         writeLog config $ "Input: " ++ input
                         return input

runProgram :: Config -> IO ()
runProgram config = do input <- getUserInput config
                       putStrLn $ "You wrote: " ++ input

Remarquez que dans les fonctions de haut niveau, nous devons constamment faire circuler la configuration.

Option 2. Monade de lecture

Une alternative est de réécrire en utilisant le Reader monade. Cela complique un peu les fonctions de bas niveau :

type Program = ReaderT Config IO

readLog :: Program String
readLog = do C logFile <- ask
             readFile logFile

writeLog :: String -> Program ()
writeLog message = do C logFile <- ask
                      x <- readFile logFile
                      writeFile logFile $ x ++ message

Mais comme notre récompense, les fonctions de haut niveau sont plus simples, car nous n'avons jamais besoin de nous référer au fichier de configuration.

getUserInput :: Program String
getUserInput = do input <- getLine
                  writeLog $ "Input: " ++ input
                  return input

runProgram :: Program ()
runProgram = do input <- getUserInput
                putStrLn $ "You wrote: " ++ input

Aller plus loin

Nous pourrions réécrire les signatures de type de getUserInput et runProgram comme suit

getUserInput :: (MonadReader m, MonadIO m) => m String

runProgram :: (MonadReader m, MonadIO m) => m ()

ce qui nous donne beaucoup de flexibilité pour plus tard, si nous décidons que nous voulons changer le sous-jacent Program pour n'importe quelle raison. Par exemple, si nous voulons ajouter un état modifiable à notre programme, nous pouvons redéfinir

data ProgramState = PS Int Int Int

type Program a = StateT ProgramState (ReaderT Config IO) a

et nous n'avons pas à modifier getUserInput ou runProgram Ils continueront à fonctionner correctement.

N.B. Je n'ai pas vérifié la frappe de ce message, et encore moins essayé de l'exécuter. Il peut y avoir des erreurs !

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