2017-07-21 86 views
6

我正在學習monad變壓器,我很困惑什麼時候使用電梯是必要的。 假設我有以下代碼(它沒有做任何有趣的事情,只是我可以用來演示的最簡單的東西)。當monad變壓器需要提升嗎?

foo :: Int -> State Int Int 
foo x = do 
    (`runContT` pure) $ do 
    callCC $ \exit -> do 
     when (odd x) $ do 
     -- lift unnecessary 
     a <- get 
     put $ 2*a 
     when (x >= 5) $ do 
     -- lift unnecessary, but there is exit 
     a <- get 
     exit a 
     when (x < 0) $ do 
     -- lift necessary 
     a <- lift $ foo (x + 10) 
     lift $ put a 

     lift get 

因此,有一個單子棧,其中主DO塊具有類型ContT Int (StateT Int Identity) Int

現在,在第三個when做遞歸阻塞程序需要一個電梯編譯。在第二個街區,沒有電梯需要,但我不知何故假設這是因爲存在exit,它以某種方式迫使線上線提升到ContT。但在第一個街區,不需要電梯。 (但是,如果明確添加,也沒有問題。)這真讓我感到困惑。我覺得所有的塊都是等效的,無論是在任何地方或任何地方都需要電梯。但這顯然不是真的。需要/不需要升降梯的關鍵區別在哪裏?

回答

11

這裏的混亂是因爲你使用的monad變換器庫有點聰明。具體而言,getput的類型沒有明確提及StateStateT。相反,它們是沿着線

get :: MonadState s m => m s 
put :: MonadState s m => s -> m() 

只要所以我們用這一個方面與MonadState實施單子沒有必要明確lift秒。這是在你使用get/put因爲

instance MonadState s (StateT s m) 
instance MonadState s m => ContT k m 

既保留的所有實例的情況。換句話說,類型分辨率會自動處理爲你做適當的提升。這反過來意味着你可以在程序結束時在get/put上隱去lift

這不會發生在您的遞歸調用中,因爲它的類型明確地爲State Int Int。如果你把它推廣到MonadState Int m => m Int你甚至可以退出這個最後的升降機。

6

我想提供一個既膚淺又同時重要的備選答案。

lift使事物類型檢查,否則你不需要使用lift

是的,這聽起來很膚淺,似乎缺乏深刻的含義。但這並不完全正確。 MonadTrans是一個能夠以單調的方式將單調行爲提升到更大環境的事物。如果你想要一個技術性的描述,那麼階級法則就「中性」的含義提供了更明確的規則。但結果是lift除了使所提供的動作與另一種類型兼容所需的內容之外別無他處。

那麼 - lift是做什麼的?它提供了將單子動作提升爲更大類型所必需的邏輯。你什麼時候需要使用它?當你有一個monadic行動,你需要提升到一個更大的類型。你什麼時候有一個單子動作需要提升成更大的類型?當這就是這些類型告訴你的。

這是使用Haskell的關鍵部分。你可以模塊化你對代碼的理解。類型系統會爲您記錄大量的簿記。依靠它來獲得正確的簿記,所以你只需要保持頭腦中的邏輯。編譯器和類型系統是作爲心理放大器工作的。他們照顧的越多,在編寫軟件時就需要留意的越少。