-- u : input type d : output type newtypeCoroutine r u d a = Coroutine { runCoroutine :: (a -> r) -> r } derivingFunctor
dataCommand r u d a = Done a | Out d (Coroutine r u d a) | In (\u -> Coroutine r u d a) derivingFunctor
这里详细的解释一下。Coroutine实际上和Cotinuation是一个东西,只不过多了额外的两个类型参数。u为输入参数的类型,d为输出参数的类型。Command r u d a里则定义了三种基本类型,分别为Done a表示计算完成,没有输入输出;Out d (Coroutine r u d a)表示输出类型为d的数据,同时执行参数中的计算;In (\u -> Coroutine r u d a)表示需要输入数据,并在等待输入时执行参数中的计算。很优雅的抽象,对吧?下一步就是将Coroutine声明为Monad:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
-- useful alias apply = runCoroutine instanceApplicative (Coroutinerud) where pure = return (<*>) = ap instanceMonad (Coroutinerud) where return a = Coroutine ($ a) f >>= g = Coroutine $ \k -> apply f $ \a -> case a of Done x -> (g x) `apply` k Out d c -> k $ Out d (c >>= g) In fn -> k $ In (\u -> fn u >>= g)
这里在做的实际上和原来并没有太大区别,只是在每一个lambda表达式中多了一个判断。
如果计算完成了(Done a),那么直接进行下一个计算。
如果计算需要输出类型为d的数据(`Out d (Coroutine r u d a)),那么挂起这个计算,直到另一个计算返回结果,并将函数应用到这个结果上。
output :: a -> Coroutine r u a () input :: Coroutine r v d v produce :: [a] -> Coroutine r u a () consume :: Coroutine [t] u t a -> [t] filterC :: (v -> Bool) -> Coroutine r v v () limit :: Int -> Coroutine r v v () suppress :: Int -> Coroutine r v v ()
-- alias mkCoroutine :: Command r u d a -> Coroutine r u d a mkCoroutine command = Coroutine ($ command)
先来看output,我们希望它立刻输出一些东西,于是可以写出:
1 2
output :: a -> Coroutine r u a () output v = mkCoroutine $ Out v (return ())
然后是input,它在获取一个输入后返回它:
1 2
input :: Coroutine r v d v input u = mkCoroutine $ In (\u -> return u)
produce连续的产生输出,而consume将所有的Out提取出来:
1 2 3 4 5 6 7 8
produce :: [a] -> Coroutine r u a () produce xs = mapM_ output xs
consume :: Coroutine [t] u t a -> [t] consume c = runCoroutine c go where go (Done _) = [] go (Out d n) = d : consume n go (In _) = []
filterC挑选出每一个符合条件的输入并输出:
1 2 3 4 5
filterC :: (v -> Bool) -> Coroutine r v v () filterC p = do i <- input when (p i) (output i) filterC p
最后,limit类似于take,suppress类似于drop:
1 2 3 4 5 6 7
limit :: Int -> Coroutine r v v () limit n = replicateM_ n (input >>= output)
suppress :: Int -> Coroutine r v v () suppress n | n > 0 = input >> suppress (n-1) | otherwise = forever $ input >>= output