Как создать собственную монаду читателя вместе с пользовательским классом типов?

Я пытаюсь объединить подход, приведенный на http://lexi-lambda.github.io/blog/2016/06/12/four-months-with-haskell/ (раздел под названием "Классы типов могут эмулировать эффекты"), с некоторыми этакий доморощенный читатель монада.

Общая проблема, которую я пытаюсь решить, состоит в том, чтобы избежать передачи переменной конфигурации, которая почти всегда функционирует в моем приложении. И причина, по которой я не могу использовать ReaderT потому что многие мои функции находятся в SqlPersistT, который сам использует ReaderT внутренне. Другая причина состоит в том, чтобы выучить всю эту умственную гимнастику лучше.

Мои два вопроса даны как комментарии в коде ниже. Также воспроизводим их здесь:

  • Что является наиболее подходящим способом определения 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

1 ответ

Решение

Вот некоторый код, который показывает, как работать с несколькими средами 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
Другие вопросы по тегам