我想我可能在某个时候在Haskell-Cafe上问过这个问题,但是如果我现在能找到答案那该死的......所以我在这里再问一遍,所以希望将来我能找到< / em>答案!
Haskell在处理参数多态时非常棒。但麻烦的是并非一切都是参数化的。作为一个简单的例子,假设我们想要从容器中获取数据的第一个元素。对于参数类型,这是微不足道的:
class HasFirst c where first :: c x -> Maybe x instance HasFirst [] where first [] = Nothing first (x:_) = Just x
现在尝试为ByteString
编写一个实例。你不能。它的类型没有提到元素类型。您也无法为Set
编写实例,因为它需要Ord
约束 - 但是类头没有提到元素类型,因此您无法限制它。
关联类型提供了一种完整解决这些问题的简洁方法:
class HasFirst c where type Element c :: * first :: c -> Maybe (Element c) instance HasFirst [x] where type Element [x] = x first [] = Nothing first (x:_) = Just x instance HasFirst ByteString where type Element ByteString = Word8 first b = b ! 0 instance Ord x => HasFirst (Set x) where type Element (Set x) = x first s = findMin s
然而,我们现在遇到了一个新问题。考虑尝试“修复”Functor
,以便它适用于所有容器类型:
class Functor f where type Element f :: * fmap :: (Functor f2) => (Element f -> Element f2) -> f -> f2
这根本不起作用。它说如果我们有一个从元素类型f
到元素类型f2
的函数,那么我们可以将f
转换为f2
。到现在为止还挺好。但是,显然无法要求f
和f2
是同样的容器!
根据现有的Functor
定义,我们有
fmap :: (x -> y) -> [x] -> [y] fmap :: (x -> y) -> Seq x -> Seq y fmap :: (x -> y) -> IO x -> IO y
但我们不有fmap :: (x -> y) -> IO x -> [y]
。那是不可能的。但上面的类定义允许它。
有人知道如何向类型系统解释我实际上的含义吗?
修改
以上工作原理是定义一种从容器类型计算元素类型的方法。如果你试图以相反的方式做到这一点会发生什么?定义一个从元素类型计算容器类型的函数?那会更容易吗?
答案 0 :(得分:22)
嗯,问题在于,不清楚修订后的Functor
是什么意思。例如,考虑ByteString
。只能通过将每个ByteString
元素替换为相同类型的元素来映射Word8
。但Functor
适用于参数可映射结构。这里有两个相互矛盾的映射概念:
所以,在这种情况下,你无法向类型系统解释你的意思,因为它没有多大意义。但是,你可以改变你的意思:))
使用类型系列很容易表达刚性映射:
class RigidMap f where
type Element f :: *
rigidMap :: (Element f -> Element f) -> f -> f
就参数化映射而言,有多种方法可以做到这一点。最简单的方法是按原样保留当前Functor
。这些类一起涵盖ByteString
,[]
,Seq
等结构。但是,由于值Set
约束,它们都会落在Map
和Ord
上。令人高兴的是,GHC 7.4中的constraint kinds扩展让我们解决了这个问题:
class RFunctor f where
type Element f a :: Constraint
type Element f a = () -- default empty constraint
fmap :: (Element f a, Element f b) => (a -> b) -> f a -> f b
在这里,我们说每个实例都应该有一个关联的类型类约束。例如,Set实例将具有Element Set a = Ord a
,以表示只有Set
实例可用于该类型时才能构造Ord
。可以在=>
的左侧显示任何内容。我们可以完全按原样定义我们之前的实例,但我们也可以执行Set
和Map
:
instance RFunctor Set where
type Element Set a = Ord a
fmap = Set.map
instance RFunctor Map where
type Element Map a = Ord a
fmap = Map.map
但是,必须使用两个独立的接口进行严格的映射和受限制的参数映射,这非常烦人。事实上,后者是不是前者的概括?考虑Set
之间的区别,Ord
只能包含ByteString
和Word8
的实例,而HasFirst
只能包含class Mappable f where
type Element f :: *
type Result f a r :: Constraint
map :: (Result f a r) => (Element f -> a) -> f -> r
s。当然,我们可以将其表达为另一种约束?
我们对Result f a r
应用相同的技巧(即为整个结构提供实例并使用类型族来公开元素类型),并引入一个新的关联约束族:
Ord a
这里的想法是a
表达它对值类型所需的约束(如Result [a] b r
),而也约束生成的容器类型,但它需要;大概是为了确保它具有r
s的同类容器的类型。例如,[b]
可能要求Result ByteString b r
为b
,Word8
将要求r
为ByteString
,(a ~ b) => ...
}是a
。
类型族已经在这里给出了我们需要表达的“是”:类型相等约束。我们可以说b
要求instance Mappable [a] where
type Element [a] = a
type Result [a] b r = r ~ [b]
-- The type in this case works out as:
-- map :: (r ~ [b]) => (a -> b) -> [a] -> r
-- which simplifies to:
-- map :: (a -> b) -> [a] -> [b]
map = Prelude.map
instance Mappable ByteString where
type Element ByteString = Word8
type Result ByteString a r = (a ~ Word8, r ~ ByteString)
-- The type is:
-- map :: (b ~ Word8, r ~ ByteString) => (Word8 -> b) -> ByteString -> r
-- which simplifies to:
-- map :: (Word8 -> Word8) -> ByteString -> ByteString
map = ByteString.map
instance (Ord a) => Mappable (Set a) where
type Element (Set a) = a
type Result (Set a) b r = (Ord b, r ~ Set b)
-- The type is:
-- map :: (Ord a, Ord b, r ~ Set b) => (a -> b) -> Set a -> r
-- (note the (Ord a) constraint from the instance head)
-- which simplifies to:
-- map :: (Ord a, Ord b) => (a -> b) -> Set a -> Set b
map = Set.map
和{{1}}属于同一类型。当然,我们可以在约束族定义中使用它。所以,我们拥有所需的一切;对实例:
{{1}}
完美!我们可以为我们想要的任何类型的容器定义实例,刚性,参数或参数但受限制,并且类型完美地运行。
免责声明:我还没有尝试过GHC 7.4,所以我不知道这些是否实际编译或工作,但我认为基本的想法是合理的。
答案 1 :(得分:6)
你需要constraint kinds,可在ghc 7.4中找到。