以下是代码段:
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
module Main
where
import Control.Exception
import System.IO
main :: IO ()
main = putStrLn "Hello World"
class IConn a where
execute :: a -> IO ()
delete :: a -> IO ()
data ConnA = ConnA
instance IConn ConnA where
execute _ = putStrLn "Execute A"
delete _ = putStrLn "Delete A"
data ConnB = ConnB
instance IConn ConnB where
execute _ = putStrLn "Execute B"
delete _ = putStrLn "Delete B"
class IConn (Conn b) => IBackend b where
type Conn b :: *
create :: b -> IO (Conn b)
withConn :: b -> Int -> Int -> (Conn b -> IO a) -> IO a
withConn b l u f = do
putStrLn $ "low: " ++ show l
putStrLn $ "up: " ++ show u
bracket (create b) delete f
data BackendA = BackendA
data BackendB = BackendB
instance IBackend BackendA where
type Conn BackendA = ConnA
create _ = return ConnA
instance IBackend BackendB where
type Conn BackendB = ConnB
create _ = return ConnB
data Backend = forall b. IBackend b => Backend b
func :: IConn c => c -> IO ()
func c = do
putStrLn "Beginning of func."
execute c
putStrLn "end of func."
createBackend :: String -> IO Backend
createBackend "A" = return $ Backend BackendA
createBackend "B" = return $ Backend BackendB
test :: String -> IO ()
test name =
createBackend name >>= case
Backend imp
-> withConn imp 10 100 func
如果我不包装createBackend
在数据Backend
中返回的IBackend
,则createBackend
函数将不会编译。但是现在,我必须使用test
函数中的案例语句从Backend
卸下IBackend
。有点麻烦。提高test
或createBackend
功能的任何建议?
改进的一种方法是制作IBackend
的CC_11实例。那么test
功能将是:
test name = do
imp <- createBackend name
withConn imp 10 100 func
好吧,让我们尝试实例化:
instance IBackend Backend where
type Conn Backend = ?
create (Backend imp) = create imp
withConn (Backend imp) = withConn imp
,但是我们对关联类型的Conn
有问题。解决其建立连接的存在类型的一种方法。
data WrapConn = forall c . IConn c => WrapConn c
instance IConn WrapConn where
execute (WrapConn c) = execute c
delete (WrapConn c) = delete c
instance IBackend Backend where
type Conn Backend = WrapConn
create (Backend imp) = WrapConn <$> create imp
withConn (Backend imp) x y f = withConn imp x y (f . WrapConn)
另一种方法是了解您的功能createBackend
将在添加新的后端时更新。正如@chi所写,您可以通过Gadt Witch替换后端名称类型,代表使用构造的参数的后端类型,例如:
data Backend imp where
ImpA :: Backend BackendA
ImpB :: Backend BackendB
ImpC :: OptionsC -> Backend BackendB
...
createBackend :: Backend imp -> IO imp
createBackend ImpA = return BackendA
createBackend ImpB = return BackendB
createBackend (ImpC options) = ...
...
test :: IBackend imp => Backend imp -> IO ()
test b = do
imp <- createBackend b
withConn imp 10 100 func