2012-01-29 34 views
45

我有一個問題,非常適合使用堆棧的MT(甚至一個MT)超過IO。除了在每個動作之前使用電梯都非常煩人之外,一切都很好!我懷疑這件事真的沒有什麼可做,但我想我會問。避免與Monad變壓器電梯

我知道提升整個塊,但如果代碼真的是混合類型呢?如果GHC放入一些句法糖(例如,<-$ = <- lift)會不會很好?

回答

51

對於所有標準mtl monads,根本不需要liftgetput,ask,tell - 它們都可以在任何單元中使用正確的變壓器在堆棧中的某處。缺失的部分是IO,即使在那裏liftIO也會將任意IO操作提升到任意數量的層。

這可以通過類型分類爲每個「效果」提供:例如,MonadState提供了getput。如果你想創建一個圍繞變壓器棧自己newtype的包裝,你可以做deriving (..., MonadState MyState, ...)GeneralizedNewtypeDeriving延長,或推出自己的實例:

instance MonadState MyState MyMonad where 
    get = MyMonad get 
    put s = MyMonad (put s) 

您可以使用此有選擇地暴露或隱藏組合互感器部件通過定義一些實例而不是其他實例。 (你可以很容易地將這種方法擴展到你自己定義的全新monadic效果,通過定義你自己的類型類併爲標準變形金剛提供樣板實例,但是全新的monads很少見;大多數情況下,通過簡單地構造由mtl提供的標準集合即可獲得)。

+0

哦,我覺得我覺得自己很愚蠢,你在之前的回答中提到,我當時無法理解它。現在,我非常感謝! – aelguindy 2012-01-29 16:51:59

43

您可以通過使用typeclasses而不是具體monad棧來使函數monad-agnostic。

比方說,你有這樣的功能,例如:

bangMe :: State String() 
bangMe = do 
    str <- get 
    put $ str ++ "!" 
    -- or just modify (++"!") 

當然,你知道,它可以作爲一個變壓器一樣,所以人們可以寫:

bangMe :: Monad m => StateT String m() 

然而,如果你有一個使用不同堆棧的函數,比如說ReaderT [String] (StateT String IO)()或其他什麼,那麼你將不得不使用可怕的lift函數!那麼如何避免?

訣竅是使函數簽名更通用,以便它說State monad可以出現在monad堆棧中的任何位置。這是這樣完成的:

bangMe :: MonadState String m => m() 

這迫使m是支持狀態(幾乎)一個單子的單子堆棧的任何地方,而且功能將不提起任何這樣的堆棧因而工作。

雖然有一個問題,因爲IO不是mtl的一部分,所以它沒有變壓器(例如IOT),也沒有默認的便捷型類。那麼當你想任意提升IO操作時你應該怎麼做?

爲了解救來自MonadIO!它的行爲幾乎與MonadState,MonadReader等一樣,唯一的區別是它有一個稍微不同的提升機制。它的工作方式如下:您可以採取任何IO操作,並使用liftIO將其變爲monad不可知的版本。所以:

action :: IO() 
liftIO action :: MonadIO m => m() 

通過把所有你想以這種方式使用的一元行動,你可以儘可能多的,只要你想糾結的單子,沒有任何繁瑣的提升。

+0

感謝您的詳細解答!在時間上被ehird毆打;) – aelguindy 2012-01-29 16:52:26

+4

我和ehird爲這個問題提供了不同的解決方案。這可能是值得閱讀這兩個答覆,以瞭解你的替代方案:) – dflemstr 2012-01-29 16:54:53

+1

這是不幸的,如此多的樣板需要。 – rightfold 2015-04-25 12:17:02