我正在围绕 Warp 服务器编写一个包装器,用户可以在其中指定路由和处理程序来创建 Web 服务器。我决定尝试使用 Continuation Monads 来允许处理程序在路由匹配时使用延续退出。以下是我开始使用的类型和定义:
import Control.Monad.Cont
import Control.Monad.Reader
import qualified Network.Wai as W
import qualified Data.Text as T
type App r a = ContT r (ReaderT W.Request IO) a
type Handler a = ReaderT W.Request IO a
type Respond = ((Status, T.Text) -> App (Status, T.Text) ())
route :: T.Text -> Handler (Status, T.Text) -> Respond -> App (Status, T.Text) ()
route routePath handler respond = do
liftIO $ print $ "Checking" `T.append` routePath
pth <- path
when (routePath == pth) $ do
req <- ask
response <- liftIO $ runReaderT handler req
respond response
一个app是一个路由的集合,每个路由从Reader环境中读取当前的continuation;我最初是这样写的:
hello :: Handler (Status, T.Text)
hello = return (ok200, "Hello World!")
goodbye :: Handler (Status, T.Text)
goodbye = return (ok200, "Goodbye World!")
app :: Respond -> App (Status, T.Text) ()
app = do
route "/hello" hello
route "/goodbye" goodbye
奇怪的是,这似乎不起作用,它只打印“Checking /goodbye”;但是,如果我们改为以下一种形式编写阅读器,则它可以正常工作,据我所知,这两个定义应该是等效的;但显然我错过了一些东西:
app :: Respond -> App (Status, T.Text) ()
app resp = do
route "/hello" hello resp
route "/goodbye" goodbye resp
有什么方法可以让我使用原始应用程序定义获得正确的行为?Reader Monad 是否以某种方式弄乱了延续?
我怀疑 reader 的 monad 定义以某种方式中断了计算顺序;但我不清楚如何:
instance Monad ((->) r) where
f >>= k = \ r -> k (f r) r
谢谢!