这是代码段:
{-# 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
如果我没有将IBackend
返回的createBackend
包装在数据Backend
中,则createBackend
函数将无法编译。但现在我必须在test
函数中使用案例陈述来从IBackend
取消装箱Backend
。这有点麻烦。有关改进test
或createBackend
功能的建议吗?
答案 0 :(得分:0)
改进的一种方法是制作Backend
IBackend
实例。然后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替换后端名称类型,这将代表构造它的参数的后端类型,如下所示:
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