2016-06-26 85 views
1

我試圖結合在http://lexi-lambda.github.io/blog/2016/06/12/four-months-with-haskell/(標題爲「Typeclasses可以模擬效果」一節)給出的方法與某種本土讀者monad。如何構建自定義閱讀器monad以及自定義類型類別?

我試圖解決的總體問題是避免傳遞一個配置變量幾乎在我的應用程序中運行。而我不能使用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 
+0

這將有助於如果你包括你的進口。 – ErikR

回答

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類型和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 
+0

謝謝@ErikR。如果我想在不使用常規ReaderT monad的情況下執行此操作,該怎麼辦?我的方法完全不可行嗎?任何幫助完成我上面的方法? –

+0

從概念上說,這是基於哪種類型的環境被問到的工作?如果它是一個'String',讓外部模塊(或內部?)處理它,如果它是'Int'則讓內部單元(或外部?)處理它。這似乎應該被歸類爲反模式或代碼異味。對我來說這件事似乎並不合適。您對使用http://lexi-lambda.github.io/blog/2016/06/12/four-months-with-haskell/中提供的方法有什麼看法? –

+0

答覆已更新。如果你願意,你可以創建你自己的monad,但是你不必僅僅因爲你想在另一個ReaderT上創建一個ReaderT。 – ErikR

相關問題