2017-01-24 1 views
2

Ich habe den Code unten, genommen von here:Verwenden von ReaderT und runReaderT mit 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") 

I

1) die genaue Rolle von readerT in data Blog a zu verstehen bin versucht?

2) genau was macht runReaderT hier?

3) Wie funktioniert die ask Funktion?

Hat jemand eine einfache Erklärung? Dies ist mein erstes Mal mit der Reader Monade arbeiten.

Antwort

2

1) In diesem Beispiel besteht der Zweck von ReaderT darin, den Funktionen einen Wert vom Typ SQLiteHandle zur Verfügung zu stellen, ohne jeder Funktion einen zusätzlichen Parameter hinzuzufügen.

2) runReaderT "wickelt" die ReaderT: newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a}. Wie Sie sehen können, ist die echte Darstellung r -> m a: eine Funktion von dem bereitgestellten Element des Typs r zu dem m a, von dem Sie dachten, dass Sie direkt damit zu tun hatten. So vermeidet ReaderT nicht wirklich die Tatsache, dass ein neuer Parameter zu Ihren Funktionen hinzugefügt werden muss; es versteckt es nur für dich.

3) runReaderT ask == runReaderT $ ReaderT return == return == r -> m r So bietet ask Zugriff auf die "Umgebung" r (der zusätzliche Parameter) einfach durch Einwickeln in die zugrunde liegende Monade.

Hier ist ein sehr einfaches (zugegebenermaßen zu einfach realistisches) Beispiel.

type ModeFlag = Int 

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

entspricht

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

Die Nützlichkeit dieser Technik nicht zu mir sofort klar war, als ich Haskell begann zu lernen. Berücksichtigen Sie jedoch den Fall, in dem Sie viele Konfigurationsparameter haben, oder Sie müssen möglicherweise bald weitere Konfigurationsparameter hinzufügen. Das Hinzufügen neuer Argumente zu Funktionen ist sehr unpraktisch. Packen Sie stattdessen einfach Ihre Konfigurationswerte in einen Datensatz und stellen Sie ihn in Ihrer gesamten Anwendung über ReaderT bereit. Es gibt eine Funktion namens asks, die wie ask ist, aber auch eine Funktion für den Wert r übernimmt. Dies kann verwendet werden, um bestimmte Felder aus einem Datensatz zu extrahieren.

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 

Es gibt einige weitere Beispiele für Reader und ReaderT in der Dokumentation (http://hackage.haskell.org/package/mtl-2.2.1/docs/Control-Monad-Reader.html an der Unterseite), einschließlich der local Funktion, die ziemlich cool ist, aber ich habe nicht viel verwendet.

Verwandte Themen