class Monad m => MonadCont m where callCC :: ((a -> m b) -> m a) -> m a instance MonadCont (ContT r m) where callCC = ContT.callCC
newtype ContT r m a = ContT { runContT :: (a -> m r) -> m r } instance Monad (ContT r m) where return x = ContT ($ x) m >>= k = ContT $ \c -> runContT m (\x -> runContT (k x) c)
證實ContT符合Monad法則: 1. return a >>= f ≡ f a return a >>= f ≡ ContT (\k -> k a) >>= f ≡ ContT $ \c -> runContT (ContT (\k -> k a)) (\x -> runContT (f x) c) ≡ ContT $ \c -> (\k -> k a) (\x -> runContT (f x) c) ≡ ContT $ \c -> (\x -> runContT (f x) c) a ≡ ContT $ \c -> runContT (f a) c ≡ ContT $ runContT (f a) ≡ f a 2. m >>= return ≡ m m >>= return ≡ ContT $ \c -> runContT m (\x -> runContT (return x) c) ≡ ContT $ \c -> runContT m (\x -> runContT (ContT $ \k -> k x) c) ≡ ContT $ \c -> runContT m (\x -> (\k -> k x) c) ≡ ContT $ \c -> runContT m (\x -> c x) ≡ ContT $ \c -> runContT m c ≡ ContT $ runContT m ≡ m 3. (m >>= f) >>= g ≡ m >>= (\x -> f x >>= g) (m >>= f) >>= g ≡ (ContT $ \c -> runContT m (\x -> runContT (f x) c)) >>= g ≡ ContT $ \c -> runContT (ContT $ \c -> runContT m (\x -> runContT (f x) c)) (\x -> runContT (g x) c) ≡ ContT $ \c -> (\c -> runContT m (\x -> runContT (f x) c)) (\x -> runContT (g x) c) ≡ ContT $ \c -> runContT m (\x -> runContT (f x) (\x -> runContT (g x) c)) m >>= (\x -> f x >>= g) ≡ ContT $ \c -> runContT m (\x -> runContT ((\x -> f x >>= g) x) c) ≡ ContT $ \c -> runContT m (\x -> runContT (f x >>= g) c) ≡ ContT $ \c -> runContT m (\x -> runContT (ContT $ \c -> runContT (f x) (\x -> runContT (g x) c)) c) ≡ ContT $ \c -> runContT m (\x -> (\c -> runContT (f x) (\x -> runContT (g x) c)) c) ≡ ContT $ \c -> runContT m (\x -> runContT (f x) (\x -> runContT (g x) c))
instance MonadTrans (ContT r) where lift m = ContT (m >>=) instance (MonadIO m) => MonadIO (ContT r m) where liftIO = lift . liftIO
lift m 將封裝在內部 Monad m 的值封裝進了 ContT Monad 之中。
lift m = ContT $ \k -> m >>= k
這裏 m 的類型爲 m a, k 的類型爲 a -> m r,m >> k 的類型爲 m r,
\k -> m >>= k 的類型爲 (a -> m r) -> m r,
ContT $ \k -> m >>= k 也就是 ContT (m >>=) 的類型爲 ContT r m a。函數
證實 ContT 中 lift 函數的定義符合 lift 的法則。 1. lift . return ≡ return lift . return $ a ≡ lift (m a) ≡ ContT (m a >>=) ≡ ContT $ \k -> m a >>= k ≡ ContT $ \k -> k a ≡ return a 2. lift (m >>= f) ≡ lift m >>= (lift . f) 假設 m = n a 而且 f a = n b 因而 m >>= f = n b lift (m >>= f) ≡ lift (n b) ≡ ContT (n b >>=) ≡ ContT $ \k -> n b >>= k ≡ ContT $ \k -> k b ≡ return b lift m >>= (lift . f) ≡ ContT (n a >>=) >>= \x -> ContT (f x >>=) ≡ ContT (\k -> k a) >>= \x -> ContT (\k -> f x >>= k) ≡ ContT $ \c -> runContT $ ContT (\k -> k a) (\x -> runContT $ ContT (\k -> f x >>= k) c) ≡ ContT $ \c -> (\k -> k a) (\x -> (\k -> f x >>= k) c) ≡ ContT $ \c -> (\x -> (\k -> f x >>= k) c) a ≡ ContT $ \c -> (\k -> f a >>= k) c ≡ ContT $ \c -> (\k -> n b >>= k) c ≡ ContT $ \c -> (\k -> k b) c ≡ ContT $ \c -> c b ≡ return b
Prelude Control.Monad.Trans.Cont Control.Monad.Trans> f a = if even a then Just (a `div` 2) else Nothing Prelude Control.Monad.Trans.Cont Control.Monad.Trans> c = lift . f :: Int -> ContT Int Maybe Int Prelude Control.Monad.Trans.Cont Control.Monad.Trans> runContT (c 3) return Nothing Prelude Control.Monad.Trans.Cont Control.Monad.Trans> runContT (c 4) f Just 1 Prelude Control.Monad.Trans.Cont Control.Monad.Trans> c = lift getLine :: ContT r IO String Prelude Control.Monad.Trans.Cont Control.Monad.Trans> runContT c print abc "abc"
callCC :: ((a -> ContT r m b) -> ContT r m a) -> ContT r m a callCC f = ContT $ \c -> runContT (f (\x -> ContT $ \_ -> c x)) c
CallCC 是 Call With Current Continuation(對當前延續函數進行調用)的縮寫,它爲 ContT Monad 提供了退出的手段。
CallCC 有一個參數 f,CallCC f 返回一個 ContT Monad。
f 的類型是 (a -> ContT r m b) -> ContT r m a,也就是說 f 自己是個函數,它的返回值類型和 CallCC f 相同,都是一個ContT Monad。
f 一般採用 \exit -> do ... 這種形式。code
Prelude Control.Monad.Trans.Cont Control.Monad.Trans> runContT (callCC (\exit -> exit 1) :: ContT Int Maybe Int) return Just 1 Prelude Control.Monad.Trans.Cont Control.Monad.Trans> runContT (callCC (\exit -> do{exit 1; return 2}) :: ContT Int Maybe Int) return Just 1
也就是說只要 CallCC 的參數採用 \exit -> do {...; exit a; ...} 這種形式,
那麼該函數所返回的 ContT Monad 將無視 exit a 後面的處理流程,無條件地將 a 傳遞給外圍函數。
下面看看 CallCC 函數是如何作到這一點的。接口
從以上推導過程能夠看出 CallCC 的參數若是採用 \exit -> do {...; exit a; ...} 這種形式,
該函數所返回的 ContT Monad 確實會無視 exit a 後面的處理流程,無條件地將 a 傳遞給外圍函數。get
evalContT :: (Monad m) => ContT r m r -> m r evalContT m = runContT m return mapContT :: (m r -> m r) -> ContT r m a -> ContT r m a mapContT f m = ContT $ f . runContT m withContT :: ((b -> m r) -> (a -> m r)) -> ContT r m a -> ContT r m b withContT f m = ContT $ runContT m . f resetT :: (Monad m) => ContT r m r -> ContT r' m r resetT = lift . evalContT shiftT :: (Monad m) => ((a -> m r) -> ContT r m r) -> ContT r m a shiftT f = ContT (evalContT . f) liftLocal :: (Monad m) => m r' -> ((r' -> r') -> m r -> m r) -> (r' -> r') -> ContT r m a -> ContT r m a liftLocal ask local f m = ContT $ \c -> do r <- ask local f (runContT m (local (const r) . c))
cont :: ((a -> r) -> r) -> Cont r a cont f = ContT (\c -> Identity (f (runIdentity . c))) runCont :: Cont r a -> (a -> r) -> r runCont m k = runIdentity (runContT m (Identity . k)) evalCont :: Cont r r -> r evalCont m = runIdentity (evalContT m) mapCont :: (r -> r) -> Cont r a -> Cont r a mapCont f = mapContT (Identity . f . runIdentity) withCont :: ((b -> r) -> (a -> r)) -> Cont r a -> Cont r b withCont f = withContT ((Identity .) . f . (runIdentity .)) reset :: Cont r r -> Cont r' r reset = resetT shift :: ((a -> r) -> Cont r r) -> Cont r a shift f = shiftT (f . (runIdentity .))
Cont Monad 是 ContT Monad(轉換器) 的一個特例。it
import Control.Monad.Trans.Cont import Control.Monad (when) add :: Int -> Int -> Int add x y = x + y square :: Int -> Int square x = x * x add_cont :: Int -> Int -> Cont r Int add_cont x y = return (add x y) square_cont :: Int -> Cont r Int square_cont x = return (square x) pythagoras_cont :: Int -> Int -> Cont r Int pythagoras_cont x y = do x_squared <- square_cont x y_squared <- square_cont y add_cont x_squared y_squared pythagoras_cont' :: Int -> Int -> Cont r Int pythagoras_cont' x y = callCC $ \exit -> do when (x < 0 || y < 0) $ exit (-1) x_squared <- square_cont x y_squared <- square_cont y add_cont x_squared y_squared main = do runCont (pythagoras_cont 3 4) print -- 25 runCont (pythagoras_cont' 3 4) print -- 25 runCont (pythagoras_cont' (-3) 4) print -- -1