我是 Haskell 新手,我正在考虑如何模块化我的 Rest应用程序,它基本上在各处传递 ReaderT。我设计了一个原始的工作示例,说明如何使用 ExistentialQuantification 做到这一点(如下)。在对 relevant answer 的评论中,用户 MathematicalOrchid 声称类似的东西是一种反模式。这是反模式吗?用新手的话,你能解释一下为什么会这样并展示一个更好的选择吗?
{-# LANGUAGE ExistentialQuantification #-}
import Control.Monad.Reader
import Control.Monad.Trans
import Data.List (intersect)
data Config = Config Int Bool
data User = Jane | John | Robot deriving (Show)
listUsers = [Jane, John, Robot]
class Database d where
search :: d -> String -> IO [User]
fetch :: d -> Int -> IO (Maybe User)
data LiveDb = LiveDb
instance Database LiveDb where
search d q = return $ filter ((q==) . intersect q . show) listUsers
fetch d i = return $ if i<3 then Just $ listUsers!!i else Nothing
data TestDb = TestDb
instance Database TestDb where
search _ _ = return [Robot]
fetch _ _ = return $ Just Robot
data Context = forall d. (Database d) => Context {
db :: d
, config :: Config
}
liveContext = Context { db = LiveDb, config = Config 123 True }
testContext = Context { db = TestDb, config = Config 123 True }
runApi :: String -> ReaderT Context IO String
runApi query = do
Context { db = db } <- ask
liftIO . fmap show $ search db query
main = do
let q = "Jn"
putStrLn $ "searching users for " ++ q
liveResult <- runReaderT (runApi q) liveContext
putStrLn $ "live result " ++ liveResult
testResult <- runReaderT (runApi q) testContext
putStrLn $ "test result " ++ testResult
编辑:基于接受的答案的工作示例
import Control.Monad.Reader
import Control.Monad.Trans
import Data.List (intersect)
data Config = Config Int Bool
data User = Jane | John | Robot deriving (Show)
listUsers = [Jane, John, Robot]
data Database = Database {
search :: String -> IO [User]
, fetch :: Int -> IO (Maybe User)
}
liveDb :: Database
liveDb = Database search fetch where
search q = return $ filter ((q==) . intersect q . show) listUsers
fetch i = return $ if i<3 then Just $ listUsers!!i else Nothing
testDb :: Database
testDb = Database search fetch where
search _ = return [Robot]
fetch _ = return $ Just Robot
data Context = Context {
db :: Database
, config :: Config
}
liveContext = Context { db = liveDb, config = Config 123 True }
testContext = Context { db = testDb, config = Config 123 True }
runApi :: String -> ReaderT Context IO String
runApi query = do
d <- fmap db $ ask
liftIO . fmap show $ search d $ query
main = do
let q = "Jn"
putStrLn $ "searching users for " ++ q
liveResult <- runReaderT (runApi q) liveContext
putStrLn $ "live result " ++ liveResult
testResult <- runReaderT (runApi q) testContext
putStrLn $ "test result " ++ testResult
最佳答案
当您在 Context
上进行模式匹配时, 你进入 db
字段一个你永远无法准确知道的类型的值;你只能知道它是一个 Database
实例,因此您可以使用该类的方法。但这意味着,从 Context
的角度来看类型,存在的 d
type 提供的功能并不比这种类型多:
-- The "record of methods" pattern
data Database =
Database { search :: String -> IO [User]
, fetch :: Int -> IO (Maybe User)
}
liveDb :: Database
liveDb = Database search fetch
where search d q = return $ filter ((q==) . intersect q . show) listUsers
fetch d i = return $ if i<3 then Just $ listUsers!!i else Nothing
testDb :: Database
testDb = Database search fetch
where search _ _ = return [Robot]
fetch _ _ = return (Just Robot)
data Context =
Context { db :: Database
, config :: Config
}
这是反对以您所做的方式使用存在类型的核心论点 - 有一个完全等效的替代方案不需要存在类型。
关于haskell - 在 Haskell 中,使用 ExistentialQuantification 的依赖注入(inject)是一种反模式吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31577342/