多参数class的推导问题

1
newtype ServeFor site param a = ServeFor (ReaderT (ServiceContext site param) (HandlerFor site) a) 

该类型是一个多参数类型,现在需要对其写个liftS,使得ServeFor可以提升到其他Monad

1
2
3
4
5
class Monad m => MonadServe site param m where
liftS :: ServeFor site param a -> m a

instance MonadServe site param (DbServeFor site param) where
liftS = DbServeFor . lift

这时候两个site和两个param并不相等,所以在DbServeFor中调用liftS并无法将ServeFor提升到DbServeFor。

所以需要添加约束使得前后的site和param相等,这时候有多种方式。

方式1:添加一个函数使用forall约束

1
2
liftSDBS :: forall site param a. ServeFor site param a -> DbServeFor site param a
liftSDBS = liftS

这种方式比较冗余,为了添加约束需要另外定义一个函数

方式2:调用的时候提供类型推导

1
2
3
4
5
6
7
8
test :: DbServeFor site param a
test po domain = do
ServiceContext userId now _ <- (liftS ctx :: DbServeFor site param)

-- 添加TypeApplications
test2 :: DbServeFor site param a
test2 po domain = do
ServiceContext userId now _ <- liftS @site @param ctx

方式3:Scoped Type Variables(推荐)

在定义class的时候就添加约束

1
2
3
4
5
6
7
8
9
class Monad m => MonadServe m where
type ServeSite m
type ServeParam m
liftS :: ServeFor (ServeSite m) (ServeParam m) a -> m a

instance MonadServe (DbServeFor site param) where
type ServeSite (DbServeFor site param) = site
type ServeParam (DbServeFor site param) = param
liftS = DbServeFor . lift

type ServeSite mtype ServeParam m不仅仅是为了转移了class的参数,还说明了这两个属性从m推导。

1
2
3
4
5
6
7
8
9
-- site param m各自独立,没有关系
class Monad m => MonadServe site param m where
liftS :: ServeFor site param a -> m a

-- ServeSite m和ServeParam m可以获取m的属性
class Monad m => MonadServe m where
type ServeSite m
type ServeParam m
liftS :: ServeFor (ServeSite m) (ServeParam m) a -> m a