使用pipes
. 我不会说它是惯用的,因为该库仍然相对较新,但我认为它完全解决了您的问题。
例如,假设您要包装某个数据库的接口:
import Control.Proxy
-- This is just some pseudo-code. I'm being lazy here
type QueryString = String
type Result = String
query :: QueryString -> IO Result
database :: (Proxy p) => QueryString -> Server p QueryString Result IO r
database = runIdentityK $ foreverK $ \queryString -> do
result <- lift $ query queryString
respond result
然后我们可以对数据库的一个接口进行建模:
user :: (Proxy p) => () -> Client p QueryString Result IO r
user () = forever $ do
lift $ putStrLn "Enter a query"
queryString <- lift getLine
result <- request queryString
lift $ putStrLn $ "Result: " ++ result
你像这样连接它们:
runProxy $ database >-> user
然后,这将允许用户从提示中与数据库进行交互。
然后我们可以使用模拟数据库切换数据库:
mockDatabase :: (Proxy p) => QueryString -> Server p QueryString Result IO r
mockDatabase = runIdentityK $ foreverK $ \query -> respond "42"
现在我们可以很容易地为模拟数据库切换数据库:
runProxy $ mockDatabase >-> user
或者我们可以切换出数据库客户端。例如,如果我们注意到一个特定的客户端会话触发了一些奇怪的错误,我们可以像这样重现它:
reproduce :: (Proxy p) => () -> Client p QueryString Result IO ()
reproduce () = do
request "SELECT * FROM WHATEVER"
request "CREATE TABLE BUGGED"
request "I DON'T REALLY KNOW SQL"
...然后像这样连接起来:
runProxy $ database >-> reproduce
pipes
允许您将流式或交互行为拆分为模块化组件,以便您可以随意混合和匹配它们,这就是依赖注入的本质。
要了解更多信息pipes
,请阅读Control.Proxy.Tutorial中的教程。