约束元素类型的构造函数类/函子?



这是一个常见问题解答——例如这个答案;或"部分类型构造函数"中的3.2节"构造良好的应用程序的约束"[Jones, Morris, Eisenberg 2020]。通常的答案是你做不到。我想我已经做到了。(但它并不漂亮。)

data Set a = NilSet | ConsSet a (Set a)    deriving (Eq, Show, Read)
mySet = ConsSet 'a' $ ConsSet 'b' $ ConsSet 'A' NilSet

我的Set类型不应该有重复的元素。如果是fmap toUpper mySet,我想避免两个'A'。然后再考虑

附加:在回应评论。(原始代码保留在下面,供参考)

@Carl[语言报告中的限制]仅适用于u类型的约束。

是的,我甚至没有尝试"确切地键入u",因为我认为它会被禁止。但是…我现在可以处理@Fyodor关于是否可能存在一些残留限制的问题:

我需要的最小语言扩展是MultiParamTypeClasses, FlexibleInstances——我很放松;他们长期/稳定/经过多次磨练的。我甚至不需要FunctionalDependencies。这也适用于Hugsmode。

class Functorish2 f  where
fmapish2 :: (FConstr2 f a b) => (a -> b) -> f a -> f b    -- huh? compiles ok
instance Functorish2 f  where                               -- needs FlexibleInstances
fmapish2 = fmapish3
class FConstr2 f a b  where fmapish3 :: (a -> b) -> f a -> f b
-- IOW same signature as fmap. This gives me a free hand.
instance (Eq b) => FConstr2 Set a b  where                  -- needs FlexibleInstances
fmapish3 f NilSet = NilSet
fmapish3 f (ConsSet x xs) = uqCons (f x) (fmapish3 f xs)
where uqCons fx xss | fElem fx xss = xss
| otherwise    = ConsSet fx xss
fElem fx (ConsSet y ys) = fx == y || fElem fx ys
fElem fx NilSet         = False

确实fmapish2 toUpper mySet会压扁重复序列。更现实的情况是,Set实现将使用BST或哈希映射等,因此对元素的约束将比Eq b更复杂。然而,似乎我可以在Set的元素上添加任何约束。

还是有陷阱?我可以滥用这个错误来导致类型不安全吗?据推测,语言报告限制(下一段)不希望允许fmapish2比类头所指示的更少多态。

令人惊讶的是我标记的那条线&;huh?&;。根据2010年语言报告第4.3.1节"类声明对方法签名的约束",cxi只能约束w-bar;特别是,cxi不能约束u."——u是来自类头的类型。(顺便说一句,Set上的deriving (Eq)不是必需的。)

第二个惊喜是,这不仅可以在GHC(8.10.2, 7.10)中工作,它也可以在Hugs(2006)中工作。

(取自原q.)

{-# LANGUAGE  MultiParamTypeClasses, FlexibleInstances, FlexibleContexts, 
UndecidableInstances   #-}
import Data.Char                                          -- toUpper
class Functorish f  where
fmapish :: (FConstr (f b)) => (a -> b) -> f a -> f b    -- huh? compiles ok
class FConstr fb  where fMerge :: fb -> fb -> fb
instance Functorish Set  where
fmapish f NilSet         = NilSet
fmapish f (ConsSet x xs) = fMerge (ConsSet (f x) NilSet) (fmapish f xs)
instance (Eq a) => FConstr (Set a)  where            
fMerge (ConsSet x NilSet) yss | fElem x yss = yss
| otherwise   = ConsSet x yss
where fElem x (ConsSet y ys) = x == y || fElem x ys
fElem x NilSet         = False

令人惊讶的是我已经标记了&;huh?&;根据2010年语言报告第4.3.1节"类声明对方法签名的约束",cxi可能只约束w-bar;特别是,cxi可能不会约束u。——u是来自类头的类型。(顺便说一句,Set上的推导(Eq)不是必需的。)

把我的评论变成一个答案,我认为相关的GHC扩展名为ConstrainedClassMethods,这是由MultiParamTypeClasses暗示的。

例如,以下代码无法编译:

class Test a where
test :: Eq a => a -> a -> Bool

你会得到错误:

Test.hs:2:3: error:
• Constraint ‘Eq a’ in the type of ‘test’
constrains only the class type variables
Enable ConstrainedClassMethods to allow it
• When checking the class method:
test :: forall a. (Test a, Eq a) => a -> a -> Bool
In the class declaration for ‘Test’
|
2 |   test :: Eq a => a -> a -> Bool
|   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

如果您启用ConstraintClassMethods,那么它将编译。

在您的例子中,ConstraintedClassMethodsMultiParamTypeClasses暗示,这也修复了这个测试示例中的错误。

但是即使没有这个,它也可以编译,因为FConstr2 f a b约束约束的不仅仅是实例头中提到的变量。参见链接的GHC指南中的这个例子,特别是op5:

class C a where
op3 :: Eq a => a -> a    -- Rejected: constrains class variable only
op4 :: D b => a -> b     -- Accepted: constrains a locally-quantified variable `b`
op5 :: D (a,b) => a -> b -- Accepted: constrains a locally-quantified variable `b`

所以约束FConstr2 f a b也可以接受,因为它约束了局部量化的变量ab

Functorish2类没有做任何有用的事情。您可以通过直接使用fmapish3实现相同的目的。签名为

fmapish2 :: (Functorish2 f, FConstr2 f a b) => (a -> b) -> f a -> f b

但是既然你给了每个f :: Type -> Type一个实例Functorish2 f,这实际上没有约束任何东西你可以再写一次

fmapish :: FConstr2 f a b => (a -> b) -> f a -> f b
fmapish = fmapish2

现在,你所有的问题(在编辑版本截至2021-08-04 01:10:56Z)是关于MultiParamTypeClasses是否足以表示约束函子。我会回答,有点。它们可以表达任何给定情况下的约束,尽管我希望总是携带三个f,ab很快就会变得尴尬。

在实践中,实例可能倾向于使用

的形式
instance (FDomCstr a, FCodomCstr b) => FConstr2 F a b where
...

所以也许这样布局会更好:

{-# LANGUAGE ConstraintKinds, TypeFamilies #-}
import Data.Kind (Type, Constraint)
class CFunctor f where
type DomCstr f a :: Constraint
type DomCstr f a = ()
type CodomCstr f b :: Constraint
type CodomCstr f b = ()
cfmap :: (DomCstr f a, CodomCstr f b) => (a -> b) -> f a -> f b
instance CFunctor Set where
type CodomCstr Set a = Ord a
cfmapish3 f NilSet = NilSet
cfmapish3 f (ConsSet x xs) = ...

最新更新