2017-02-13 37 views
2

我正在通過Monad Transformers,我瞭解它們的主要作用是提供一個monadic容器來容納不同類型的monads,它提供了一個通用接口,可以在該接口中操作計算中的「嵌套」單子。monad變壓器的使用和示例?

我已經嘗試實現自己的變壓器:

data CustomTransformer a = CustomTransformer 

class TransformerClass m a where 
    lift :: m a -> CustomTransformer (m a) 

instance TransformerClass Maybe a where 
lift (Just a) = CustomerTransformer (Just a) 

經歷this紙,我明白,這是不正確。他們的例子顯示:

class MonadTrans r where 
    lift :: Monad m => m a -> (r m) a 

這嵌套在單子轉換r m行動a

我不明白如何使用單子變換器幫助處理計算中的多個單子類型?任何人都可以提供簡單的解釋和例子嗎?

回答

3

我覺得這有助於瞭解在這裏玩的種類。

首先,如你所知,一個單子類型構造m :: * -> *有兩個操作,return :: a -> m a(>>=) :: m a -> (a -> m b) -> m b配對。

class Monad (m :: * -> *) where 
    return :: a -> m a 
    (>>=) :: m a -> (a -> m b) -> m b 

單子變壓器的想法是,他們是那種把一個單子到另一個單子類型級功能。因此,假定monad是單參數類型的構造函數* -> *,則一旦刪除小括號,則單變量必須是種類(* -> *) -> (* -> *)(* -> *) -> * -> *。單變量變量是一個雙參數類型,其第一個參數是monad,第二個參數是一個值。

更具體地說,單子變壓器是t :: (* -> *) -> * -> *型,使得無論何時m是單子,t m也是單子。我們還要求t m是一個較大的 monad比m,意思是m中的任何動作都可以嵌入到t m中。

class MonadTrans t where 
    transform :: Monad m :- Monad (t m) 
    lift :: Monad m => m a -> t m a 

我在transform定義使用the "entailment" operator :-Kmett's constraints package; transform證明mMonad意味着t mMonad。 (The version of MonadTrans in transformers忽略了transform成員,因爲它被寫入時GHC不支持:-操作。)

重要的是,t m a(又名(t m) a)意味着什麼比t (m a)不同。前者是應用於ma的雙參數類型t。後者是適用於m a的單參數類型t

一個非常簡單的 - 因爲我是我的手機上 - 例如一個什麼樣的單子轉換的定義是這樣的:

newtype IdentityT m a = IdentityT { runIdentityT :: m a } 

instance Monad m => Monad (IdentityT m) where 
    return = IdentityT . return 
    IdentityT m >>= f = IdentityT $ m >>= (runIdentityT . f) 

instance MonadTrans IdentityT where 
    transform = Sub Dict 
    lift = IdentityT 

注意IdentityT是兩個參數的數據類型;第一個參數m :: * -> *是一個monad,第二個參數a :: *是一個常規類型。

ghci> :k IdentityT 
IdentityT :: (* -> *) -> * -> * 
+0

感謝您的回答,非常清楚! –

1

不同的monads給出不同的「效果」,如狀態或非確定性。但是如果你想在monad中使用更多這樣的單元,你需要從零開始實現這樣一個monad(這會很乏味)或者以某種方式堆疊monads。變形金剛在那裏允許堆疊各種monads的效果。變形金剛通常是通過從一個提供你感興趣的效果的monad獲得的,提取效果,同時可以在其中插入另一個monad。這對其他單體來說是一種裝飾,可以爲它們增加你想要的狀態/ nondet/...效果。查看包裹transformers的瑕疵以查看一些常見的examples

變壓器捕捉相關的單子的影響,但餘地你把另一個單子,如綁定(>>=)的定義StateT:在

(>>=) :: StateT s m a -> (a -> StateT s m b) -> StateT s m b 
m >>= k = StateT $ \ s -> do 
    (a, s') <- runStateT m s 
    runStateT (k a) s' 

do塊「運行」 (m,這些行通過m的綁定運算符>>=鏈接在一起),從而「做它自己的事情」,而StateT保持側面的狀態。

通過這種方式,您可以疊加更多效果,每個變壓器都照顧一個,並獲得具有所有這些效果的monad。

ExceptT e (StateT s (ListT IO)) 

在這裏,你表達的IO提供的「真實世界」環境中「狀態」和「不確定性」計算「拋出異常」的效果。請以此爲例,IO本身是有狀態的,所以我不否認這個例子有點過分。

只需注意:具體monad可以表示爲最簡單的monad Identity的轉換,它本身沒有任何意義,就像State sStateT s Identity的形式實現。

+0

「請以此爲例,'IO'本身是有狀態的,所以我不否認這個例子有點太多了」 - 我不認爲'IO'的'StateT'過多。保持一個純粹的狀態除了'IO'之外並沒有什麼錯,它往往比周圍雜耍可變引用更令人愉快。 – duplode

+0

你說得對,那就是爲什麼我把它放在那裏,這只是一個人爲的例子,因爲我沒有想到真正的用例 – jakubdaniel

3

Monads是實現monad操作的任何多態數據類型m areturn>>=,並服從monad法則。

一些單子有一個特殊的形式,因爲m可以寫成多態mT m',並且只要參數m'是單子,它將是單子。我們可以像這樣拆分的Monad是monad變形金剛。外層monad mT,增加了monadic效果的內部monad。由於內部m',我們可以嵌套無限量的monads,本身可以是monad變壓器。

由於Maybe是最簡單的monads之一,因此我將轉發代碼Transformers

該定義顯示monad MaybeT m大部分是圍繞monad m的包裝。但是,m不再是「純」monad,而是具有受Maybe影響的類型參數。

newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) } 

現在定義Monad實例。

instance (Monad m) => Monad (MaybeT m) where 
    return = lift . return 
    x >>= f = MaybeT $ do 
     v <- runMaybeT x 
     case v of 
      Nothing -> return Nothing 
      Just y -> runMaybeT (f y) 

望着綁定操作>>=,一定要注意以下的$該做記號發生在m單子是很重要的。內部monad通過runMaybeT x恢復,monadic值綁定到v,觸發m效果。然後,評估Maybe狀態,將f應用於某個值(如果存在)並適當包裝。

對我來說,混淆的一個來源是內部和外部monad的術語 - 如果我將它倒退,我不會感到驚訝。轉換後的monad mT實際上是將自己投射到內部monad中,m

該問題詢問了關於lift,它對應於在外部monad的純上下文中運行內部monad的能力。需要注意的是lift或MonadTrans沒有定義的變壓器,但是他說,如果一個單子(t m)是變壓器,那麼你應該能夠解除m a成純(t m) a

在我的例子,下面是一些一樣機程序,其中用戶 要求一些資源。函數userGetResource要求輸入用戶名,然後在某個註冊表中查詢該名稱,如果找到該名稱,它將嘗試獲取該用戶的權限,如果給予該用戶權限,它將返回該資源。有一系列的IO操作可能會因爲Nothing而失敗。 MaybeT有助於編寫函數,使其更易於閱讀和維護。特別注意在userGetResource函數中使用lift。因爲它總是會返回一個字符串(baring catastrophe),所以這個函數被解析成MaybeT的純粹Just形式。

import Data.List(find) 
import Control.Monad (liftM) 
import Control.Monad.Trans.Maybe 
import Control.Monad.Trans.Class(lift) 

data User = User { userName :: String, hasCredentials :: Credentials } 
type Credentials = Bool 
type Token =() 
type UserReg = [User] 
data Resource = Resource deriving Show 

userGetResource :: IO (Maybe Resource) 
userGetResource = runMaybeT $ do 
    str <- lift $ do putStrLn "Who are you" 
        getLine 
    usr <- MaybeT $ getUser str 
    tok <- MaybeT $ getPermission usr 
    MaybeT $ getResource tok 


getResource :: Token -> IO (Maybe Resource) 
getResource _ = return (Just Resource) 

userRegistry :: IO UserReg 
userRegistry = return [User "Alice" True, User "Bob" False] 

lookupUser :: String -> UserReg -> Maybe User 
lookupUser name = find ((name==) . userName) 

getUser :: String -> IO (Maybe User) 
getUser str = do 
    reg <- userRegistry 
    return $ lookupUser str reg 

getPermission :: User -> IO (Maybe Token) 
getPermission usr 
    | hasCredentials usr = do 
    tok <- generateToken 
    return (Just tok) 
    | otherwise   = return Nothing 

generateToken :: IO Token 
generateToken = doSomeUsefulIO 
    where 
    doSomeUsefulIO = return() 

而且這裏是幾個電話的輸出userGetResource

失敗, 「薩姆」 不是新用戶註冊

*MaybeTrans> userGetResource 
Who are you 
Sam 
Nothing 

成功, 「愛麗絲」 在註冊表和有權限。

*MaybeTrans> userGetResource 
Who are you 
Alice 
Just Resource 

失敗。 「Bob」正在註冊,但沒有權限。

*MaybeTrans> userGetResource 
Who are you 
Bob 
Nothing