如何处理haskell中同类的返回值?

时间:2017-05-04 09:23:06

标签: haskell

这是代码段:

{-# 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。这有点麻烦。有关改进testcreateBackend功能的建议吗?

1 个答案:

答案 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