为这个有点做作的例子道歉。我试图在不失去理由的情况下尽可能简化这一点:
假设我有一个多参数类型类Relation
:
class Relation r a b where ....
以及一个在这种类型上存在量化的函数:
neighbours :: forall r a b. Relation r a b => a -> r -> Graph -> [b]
现在我介绍几个Relation
:的实例
data Person = Person String
data Pet = Pet String
data Owns = Owns
data Desires = Desires
instance Relation Owns Person Pet
instance Relation Desires Person Pet
现在,我想写一个方法,通过某种方式获取与给定人相关的所有宠物,而不暴露实现的复杂性:
data PetAttachment = AttachOwns | AttachDesires
personPets :: Person -> PetAttachment -> Graph -> [Pet]
personPets person att g =
neighbours person r g
where
r = case PetAttachment of
AttachOwns -> Owns
AttachDesires -> Desires
我的问题是:如何正确地键入r
。在没有提示的情况下,它将尝试键入Owns
,因此失败。我也可以提示它应该是存在类型的:
r :: forall r. Relation r Person Pet => r
但编译器似乎无法推断出Relation r0 Person Pet
,称r0
是模糊的:
关系。hs:26:5:没有(关系r0个人宠物)的实例因使用"邻居"而产生
类型变量"r0"的不明确
我能说服r
以这样一种方式键入,让它编译吗?
完整的可编译示例:
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
data Graph = Graph
class Relation r a b where
relation :: (r,a,b) -> Int
neighbours :: forall r a b. Relation r a b => a -> r -> Graph -> [b]
neighbours = undefined
data Person = Person String
data Pet = Pet String
data Owns = Owns
data Desires = Desires
instance Relation Owns Person Pet where
relation _ = 1
instance Relation Desires Person Pet where
relation _ = 2
data PetAttachment = AttachOwns | AttachDesires
personPets :: Person -> PetAttachment -> Graph -> [Pet]
personPets person att =
neighbours person r
where
r :: forall r. Relation r Person Pet => r
r = case att of
AttachOwns -> Owns
AttachDesires -> Desires
main :: IO ()
main = undefined
这里有两种编写方法,一种使用GADT,另一种使用Rank 2 Type。
GADT
GADT可以捕获对r
的约束,即必须存在Relation r Person Pet
的实例。要使用GADT,您需要添加
{-# LANGUAGE GADTs #-}
由于Relation r a b =>
对构造函数的约束,RelationOf a b
将在构造函数RelationOf
中捕获Relation r a b
实例。
data RelationOf a b where
RelationOf :: Relation r a b => r -> RelationOf a b
personPets
可以写得像一样随心所欲
personPets :: Person -> PetAttachment -> Graph -> [Pet]
personPets person att =
case r of (RelationOf r') -> neighbours person r'
where
r :: RelationOf Person Pet
r = case att of
AttachOwns -> RelationOf Owns
AttachDesires -> RelationOf Desires
neighbors person r
行上的case
需要从RelationOf
构造函数获取捕获的Relation r Person Pet
实例。存在限定的GADT捕获的实例只能通过与它们的模式匹配来恢复。
等级2类型
要使用等级2类型,您需要添加以下之一
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE RankNTypes #-}
对于秩为2的类型,我们可以将personPets
分为两部分。第一个将计算出r
是什么,Owns
或Desires
,并将其传递给延续传递风格的通用量化函数。
withAttachment :: PetAttachment -> (forall r. Relation r Person Pet => r -> c) -> c
withAttachment AttachOwns f = f Owns
withAttachment AttachDesires f = f Desires
personPets
将调用withAttachment
,将部分应用的neighbours
作为延续传递给它。
personPets :: Person -> PetAttachment -> Graph -> [Pet]
personPets person att = withAttachment att (neighbours person)
低技术解决方案:
personPets :: Person -> PetAttachment -> Graph -> [Pet]
personPets person att = case att of
AttachOwns -> neighbours person Owns
AttachDesires -> neighbours person Desires