2017-01-24 1 views
2

J'ai le code ci-dessous, extrait de here:Utilisation de ReaderT et runReaderT avec SQLite?

type Blog a = ReaderT SQLiteHandle IO a 
data BlogDBException = BlogDBException String deriving (Show, Typeable) 
instance Exception BlogDBException 

run :: Blog a -> IO a 
run m = do 
    db <- openConnection "myblog.db" 
    runReaderT m db --runReaderT :: ReaderT r m a -> (r -> m a) 


sql :: String -> Blog (Either String[[Row Value]]) 
sql query = do 
    db <- ask --ask :: Monad m => ReaderT r m r 
    liftIO $ do 
    putStrLn query 
    execStatement db query 


dbQuery :: Blog [Int] 
dbQuery = do 
    r <- sql "select UID from UIDS;" 
    case r of 
    Right [rows] -> return [fromIntegral uid | [(_, Int uid)] <- rows] 
    Left s -> liftIO $ throwIO (BlogDBException s) 
    _ -> liftIO $ throwIO (BlogDBException "Invalid result") 

J'essaie de comprendre

1) le rôle exact de readerT dans data Blog a?

2) exactement ce que runReaderT fait ici?

3) comment fonctionne la fonction ask?

Quelqu'un at-il une explication simple? C'est la première fois que je travaille avec la monade Reader.

Répondre

2

1) Dans cet exemple, le but de ReaderT est de mettre à la disposition des fonctions une valeur de type SQLiteHandle sans ajouter de paramètre à chaque fonction.

2) runReaderT "déballe" le ReaderT: newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a}. Comme vous pouvez le voir, la représentation réelle est r -> m a: une fonction de l'élément fourni de type r au m a que vous pensiez avoir affaire directement. Ainsi, ReaderT n'évite pas vraiment le fait qu'un nouveau paramètre doit être ajouté à vos fonctions; c'est juste de le cacher pour toi.

3) Ainsi ask fournit l'accès à "l'environnement" r (le paramètre supplémentaire) simplement en l'enveloppant dans la monade sous-jacente.

Voici un exemple très simple (certes trop simple pour être réaliste).

type ModeFlag = Int 

g :: ModeFlag -> IO() 
g modeFlag = ... -- take some action based on modeFlag 

est équivalent à

h :: ReaderT ModeFlag IO() 
h = do 
    modeFlag <- ask 
    ... -- take some action based on modeFlag 

L'utilité de cette technique n'a pas été immédiatement évident pour moi quand je commencé à apprendre Haskell. Cependant, considérez le cas où vous avez beaucoup de paramètres de configuration ou vous pouvez prévoir le besoin d'ajouter plus de paramètres de configuration bientôt. L'ajout de nouveaux arguments aux fonctions est très incommode. Au lieu de cela, il suffit d'emballer vos valeurs de configuration dans un enregistrement et de le fournir dans toute votre application via ReaderT. Il y a une fonction appelée asks qui ressemble à ask mais prend également une fonction à appliquer à la valeur r. Cela peut être utilisé pour extraire certains champs d'un enregistrement.

data Config :: Config { param1 :: Int, param2 :: String, ... other fields } 

doStuff :: ReaderT Config IO() 
doStuff = do 
    i <- asks param1 
    s <- asks param2 
    undefined -- do some stuff 

Il y a quelques exemples de Reader et ReaderT dans la documentation (http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Reader.html en bas), y compris la fonction local qui est assez cool, mais je ne l'ai pas utilisé beaucoup.