【问题标题】:How to build a custom reader monad along with a custom typeclass?如何与自定义类型类一起构建自定义阅读器 monad?
【发布时间】:2016-10-28 09:07:11
【问题描述】:

我正在尝试将http://lexi-lambda.github.io/blog/2016/06/12/four-months-with-haskell/(标题为“Typeclasses 可以模拟效果”的部分)提供的方法与某种本土阅读器 monad 结合起来。

我要解决的总体问题是避免将配置变量传递给我的应用程序中几乎所有功能。我不能使用ReaderT 的原因是因为我的很多函数都在SqlPersistT 中,它本身在内部使用ReaderT。另一个原因是更好地学习所有这些心理体操。

我的两个问题在下面的代码中以 cmets 的形式给出。在这里也复制它们:

  • 定义NwMonad 的最合适方式是什么?
  • 因此,如何将NwMonad 定义为HasNwConfig 的实例? askNwConfig的函数体怎么写?
  • 我最后怎么打电话给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

【问题讨论】:

  • 如果你包含你的导入会有所帮助。

标签: haskell monads monad-transformers reader-monad


【解决方案1】:

这里有一些代码展示了如何在同一个转换器堆栈中使用多个阅读器环境。这里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 类型并运行SqlConn 的代码:

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

【讨论】:

  • 感谢@ErikR。如果我想不使用使用常规的 ReaderT monad 怎么办?我的方法完全行不通吗?对完成我上面遵循的方法有任何帮助吗?
  • 从概念上讲,这是否基于所询问的环境类型而起作用?如果是 String 让外部 modad(或者是内部?)处理它,如果是 Int 让内部 monad(或者它是外部?)处理它。似乎这应该归类为反模式或代码异味。我觉得这里有些不对劲。您如何看待使用lexi-lambda.github.io/blog/2016/06/12/four-months-with-haskell 给出的方法?
  • 答案已更新。如果你愿意,你可以创建你自己的 monad,但你不必仅仅因为你想在另一个 ReaderT 之上创建一个 ReaderT。
  • 感谢 ErikR 再次澄清。虽然这给了我一个可行的解决方案,但它并不能帮助我理解它是如何真正起作用的。而关于嵌套 ReaderT 和链式升降机的一些事情并没有使这看起来像是一个优雅的解决方案。不能“扩展”一个单子(可能使用类型类)来“添加更多”功能吗? (这有道理吗?)
  • 我想我已经回答了你原来的问题。如果您需要更多帮助来了解 lift 的工作原理(在 mtl 库中),请提出一个新问题。
猜你喜欢
  • 2016-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-01
相关资源
最近更新 更多