haskell - 在 Haskell 中,使用 ExistentialQuantification 的依赖注入(inject)是一种反模式吗?

标签 haskell dependency-injection

我是 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/

相关文章:

php - Symfony 3 - (文件路径)的定义没有类属性并且似乎引用了全局命名空间中的类或接口(interface)

spring - 将 Spring bean 注入(inject) EJB3

haskell - 构造延续类型?

string - 显示 haskell 中重复的单词列表

haskell - "monadic function"的正式定义

java - 在自己的 Java 框架中支持多个 DI 容器

haskell - haskell 中列表列表的模式匹配

haskell - Haskell 中的目录内容

c# - 在 ASP.NET Core MVC 中注入(inject) ApplicationUser

PHP symfony4 : Dependency injection inside KernelTestCase for command