如何构建自定义 reader monad 以及自定义类型类?
How to build a custom reader monad along with a custom typeclass?
我正在尝试将 http://lexi-lambda.github.io/blog/2016/06/12/four-months-with-haskell/(标题为 "Typeclasses can emulate effects" 的部分)中给出的方法与某种本土 reader monad 结合起来。
我试图解决的总体问题是避免将配置变量传递给几乎所有应用程序中的功能。我不能使用 ReaderT
的原因是因为我的很多功能都在 SqlPersistT
中,它本身在内部使用 ReaderT
。另一个原因是为了更好地学习所有这些心理体操。
我的两个问题在下面的代码中作为注释给出。也在这里复制它们:
- 什么是最合适的定义方式
NwMonad
?
- 因此,如何将
NwMonad
定义为HasNwConfig
的实例? askNwConfig
的函数body怎么写?
- 我最终如何调用
runNwMonad
?它的论点是什么?
代码如下:
data NwConfig = NwConfig {
_googleClientId :: T.Text,
_googleClientSecret :: T.Text,
_tgramBotToken :: String,
_aria2Command :: String,
_aria2DownloadDir :: String
}
$(makeLenses ''NwConfig)
instance Default NwConfig where
def = NwConfig{}
class MonadIO m => HasNwConfig m where
askNwConfig :: m NwConfig
startAria2 :: (HasNwConfig m) => m Sytem.Process.ProcessHandle
cfg <- askNwConfig
(_, _, _, processHandle) <- createProcess $ proc (cfg ^. aria2Command) []
return processHandle
-- QUESTION: Is this correct?
data NwMonad a = NwMonad{runNwMonad :: (NwConfig -> IO a)}
deriving (Functor, Applicative, Monad, MonadIO)
-- Or is this the way to do it?
data NwMonad a = NwMonad{runNwMonad :: IO a, nwConfig :: NwConfig}
deriving (Functor, Applicative, Monad, MonadIO)
instance HasNwConfig NwMonad where
askNwConfig = return . nwConfig -- QUESTION: How to write this?
main :: IO ()
main = do
[cId, cSecret, botToken] <- sequence [getEnv "GOOGLE_CLIENT_ID", getEnv "GOOGLE_CLIENT_SECRET", getEnv "TELEGRAM_TOKEN"]
let cfg = (def :: NwConfig)
& googleClientId .~ (T.pack cId)
& googleClientSecret .~ (T.pack cSecret)
& tgramBotToken .~ botToken
& aria2Command .~ "/Users/saurabhnanda/projects/nightwatch/aria2-1.19.3/bin/aria2c"
-- QUESTION: How do I use this now?
runNwMonad $ (?????) $ startAria2
下面是一些代码,展示了如何在同一个转换器堆栈中处理多个 Reader 环境。这里 BaseMonad
就像你的 SqlPersistT
:
import Control.Monad.Reader
import Control.Monad.State
import Control.Monad.IO.Class
type BaseMonad = ReaderT String IO
type NwMonad = ReaderT Int BaseMonad
askString :: NwMonad String
askString = lift ask
askInt :: NwMonad Int
askInt = ask
startAria :: NwMonad ()
startAria = do
i <- askInt
s <- askString
liftIO $ putStrLn $ "i: " ++ show i ++ " s: " ++ s
main = do
let cfg = 10 -- i.e. your google client data
s = "asd" -- whatever is needed for SqlPersistT
runReaderT (runReaderT startAria cfg) s
下面是一些使用 SqlPersisT 类型和 runSqlConn 的代码:
import Control.Monad.Reader
import Control.Monad.State
import Control.Monad.IO.Class
import Database.Persist.Sql
data Config = Config { _clientId :: String }
type BaseMonad = SqlPersistT IO
type NwMonad = ReaderT Config BaseMonad
askBackend:: NwMonad SqlBackend
askBackend = lift ask
askConfig :: NwMonad Config
askConfig = ask
startAria :: NwMonad ()
startAria = do
cfg <- askConfig
liftIO $ putStrLn $ "client id: " ++ (_clientId cfg)
main = do
let cfg = Config "foobar"
backend = undefined :: SqlBackend -- however you get this
sqlComputation = runReaderT startAria cfg :: SqlPersistT IO ()
runSqlConn sqlComputation backend :: IO ()
更新
环境类型无关紧要。
import Control.Monad.Reader
import Control.Monad.IO.Class
type Level1 = ReaderT Int IO
type Level2 = ReaderT Int Level1
type Level3 = ReaderT Int Level2
ask3 :: Level3 Int
ask3 = ask
ask2 :: Level3 Int
ask2 = lift ask
ask1 :: Level3 Int
ask1 = lift $ lift $ ask
doit :: Level3 ()
doit = do
r1 <- ask1
r2 <- ask2
r3 <- ask3
liftIO $ print (r1, r2, r3)
main = do
runReaderT (runReaderT (runReaderT doit 333) 222) 111
我正在尝试将 http://lexi-lambda.github.io/blog/2016/06/12/four-months-with-haskell/(标题为 "Typeclasses can emulate effects" 的部分)中给出的方法与某种本土 reader monad 结合起来。
我试图解决的总体问题是避免将配置变量传递给几乎所有应用程序中的功能。我不能使用 ReaderT
的原因是因为我的很多功能都在 SqlPersistT
中,它本身在内部使用 ReaderT
。另一个原因是为了更好地学习所有这些心理体操。
我的两个问题在下面的代码中作为注释给出。也在这里复制它们:
- 什么是最合适的定义方式
NwMonad
? - 因此,如何将
NwMonad
定义为HasNwConfig
的实例?askNwConfig
的函数body怎么写? - 我最终如何调用
runNwMonad
?它的论点是什么?
代码如下:
data NwConfig = NwConfig {
_googleClientId :: T.Text,
_googleClientSecret :: T.Text,
_tgramBotToken :: String,
_aria2Command :: String,
_aria2DownloadDir :: String
}
$(makeLenses ''NwConfig)
instance Default NwConfig where
def = NwConfig{}
class MonadIO m => HasNwConfig m where
askNwConfig :: m NwConfig
startAria2 :: (HasNwConfig m) => m Sytem.Process.ProcessHandle
cfg <- askNwConfig
(_, _, _, processHandle) <- createProcess $ proc (cfg ^. aria2Command) []
return processHandle
-- QUESTION: Is this correct?
data NwMonad a = NwMonad{runNwMonad :: (NwConfig -> IO a)}
deriving (Functor, Applicative, Monad, MonadIO)
-- Or is this the way to do it?
data NwMonad a = NwMonad{runNwMonad :: IO a, nwConfig :: NwConfig}
deriving (Functor, Applicative, Monad, MonadIO)
instance HasNwConfig NwMonad where
askNwConfig = return . nwConfig -- QUESTION: How to write this?
main :: IO ()
main = do
[cId, cSecret, botToken] <- sequence [getEnv "GOOGLE_CLIENT_ID", getEnv "GOOGLE_CLIENT_SECRET", getEnv "TELEGRAM_TOKEN"]
let cfg = (def :: NwConfig)
& googleClientId .~ (T.pack cId)
& googleClientSecret .~ (T.pack cSecret)
& tgramBotToken .~ botToken
& aria2Command .~ "/Users/saurabhnanda/projects/nightwatch/aria2-1.19.3/bin/aria2c"
-- QUESTION: How do I use this now?
runNwMonad $ (?????) $ startAria2
下面是一些代码,展示了如何在同一个转换器堆栈中处理多个 Reader 环境。这里 BaseMonad
就像你的 SqlPersistT
:
import Control.Monad.Reader
import Control.Monad.State
import Control.Monad.IO.Class
type BaseMonad = ReaderT String IO
type NwMonad = ReaderT Int BaseMonad
askString :: NwMonad String
askString = lift ask
askInt :: NwMonad Int
askInt = ask
startAria :: NwMonad ()
startAria = do
i <- askInt
s <- askString
liftIO $ putStrLn $ "i: " ++ show i ++ " s: " ++ s
main = do
let cfg = 10 -- i.e. your google client data
s = "asd" -- whatever is needed for SqlPersistT
runReaderT (runReaderT startAria cfg) s
下面是一些使用 SqlPersisT 类型和 runSqlConn 的代码:
import Control.Monad.Reader
import Control.Monad.State
import Control.Monad.IO.Class
import Database.Persist.Sql
data Config = Config { _clientId :: String }
type BaseMonad = SqlPersistT IO
type NwMonad = ReaderT Config BaseMonad
askBackend:: NwMonad SqlBackend
askBackend = lift ask
askConfig :: NwMonad Config
askConfig = ask
startAria :: NwMonad ()
startAria = do
cfg <- askConfig
liftIO $ putStrLn $ "client id: " ++ (_clientId cfg)
main = do
let cfg = Config "foobar"
backend = undefined :: SqlBackend -- however you get this
sqlComputation = runReaderT startAria cfg :: SqlPersistT IO ()
runSqlConn sqlComputation backend :: IO ()
更新
环境类型无关紧要。
import Control.Monad.Reader
import Control.Monad.IO.Class
type Level1 = ReaderT Int IO
type Level2 = ReaderT Int Level1
type Level3 = ReaderT Int Level2
ask3 :: Level3 Int
ask3 = ask
ask2 :: Level3 Int
ask2 = lift ask
ask1 :: Level3 Int
ask1 = lift $ lift $ ask
doit :: Level3 ()
doit = do
r1 <- ask1
r2 <- ask2
r3 <- ask3
liftIO $ print (r1, r2, r3)
main = do
runReaderT (runReaderT (runReaderT doit 333) 222) 111