phi*_*ler 19
从GHC 7.8.1开始,可以声明封闭类型族,并且我认为在它们的帮助下ConstraintKinds,您可以这样做:
type family SecretClass (a :: *) :: Constraint where
SecretClass Int = ()
Run Code Online (Sandbox Code Playgroud)
SecretClass a 形成一个约束,相当于一个类型类,并且因为任何人都不能扩展族,所以不能定义"类"的其他实例.
(这实际上只是猜测,因为我无法测试它,但是这个有趣链接中的代码使它看起来像是可行的.)
Ben*_*Ben 14
我相信答案是肯定的,取决于你想要实现的目标.
您可以避免从接口模块1导出类型类名本身,同时仍然导出类型类函数的名称.然后没有人可以创建该类的实例,因为没有人可以命名它!
例:
module Foo (
foo,
bar
) where
class SecretClass a where
foo :: a
bar :: a -> a -> a
instance SecretClass Int where
foo = 3
bar = (+)
Run Code Online (Sandbox Code Playgroud)
缺点是没有人可以用你的类作为约束写一个类型.这并不能完全阻止人们编写具有这种类型的函数,因为编译器仍然能够推断出类型.但这会非常烦人.
您可以通过提供另一个空类型来减轻缺点,将"封闭"类作为超类.您使原始类的每个实例也成为子类的实例,并导出子类(以及所有类型类函数),但不导出超类.(为了清楚起见,你可能应该使用"公共"类而不是你公开的所有类型中的"秘密"类,但我相信它可以工作).
例:
{-# LANGUAGE FlexibleInstances, UndecidableInstances #-}
module Foo (
PublicClass,
foo,
bar
) where
class SecretClass a where
foo :: a
bar :: a -> a -> a
class SecretClass a => PublicClass a
instance SecretClass Int where
foo = 3
bar = (+)
instance SecretClass a => PublicClass a
Run Code Online (Sandbox Code Playgroud)
如果您愿意PublicClass为每个实例手动声明一个实例,则可以不使用扩展SecretClass.
现在客户端代码可以PublicClass用来编写类型类约束,但是每个实例都PublicClass需要一个SecretClass相同类型的实例,并且没有办法声明一个SecretClass没有人的新实例可以使更多的类型实例为PublicClass2.
所有这些都无法让您获得编译器将该类视为"已关闭"的能力.它仍然会抱怨模糊的类型变量可以通过选择唯一可见的"闭合"实例来解决.
1纯粹意见:通常一个单独的内部模块通常是一个好主意,它可以导出所有内容,以便您可以通过接口模块进行测试/调试,导入内部模块并只输出您想要的东西出口.
2我猜有扩展名的人可以声明一个新的重叠实例.例如,如果你提供了一个实例[a],有人可以声明一个新的PublicClassfor 实例[Int],它将搭载SecretClassfor 的实例[a].但鉴于它PublicClass没有功能,他们不能写一个实例,SecretClass我看不出那么多可以做到的.
Tox*_*ris 12
您可以通过封闭类型族编码封闭类型类,这些类可以依次编码为关联类型族.此解决方案的关键是关联类型系列的实例位于类型类实例中,并且每个单形类型只能有一个类型类实例.
请注意,此方法独立于模块系统.我们提供了一个明确的列表,列出了哪些实例是合法的,而不是依赖于模块边界.这意味着,一方面,法律实例可以分布在多个模块甚至包上,另一方面,即使在同一模块中也不能提供非法实例.
对于这样的回答,我认为我们要关闭下面的类,使其只能被实例化的类型Int和Integer,但不支持其他类型的:
-- not yet closed
class Example a where
method :: a -> a
Run Code Online (Sandbox Code Playgroud)
首先,我们需要一个用于将闭合类型族编码为关联类型族的小框架.
{-# LANGUAGE TypeFamilies, EmptyDataDecls #-}
class Closed c where
type Instance c a
Run Code Online (Sandbox Code Playgroud)
该参数c代表类型系列的名称,参数a是类型系列的索引.cfor 的族实例a编码为Instance c a.由于c也是类参数,因此c必须在单个类实例声明中一起给出所有族实例.
现在,我们使用这个框架来定义一个封闭的家庭类型MemberOfExample来编码Int和Integer是Ok,和所有其他类型都没有.
data MemberOfExample
data Ok
instance Closed MemberOfExample where
type Instance MemberOfExample Int = Ok
type Instance MemberOfExample Integer = Ok
Run Code Online (Sandbox Code Playgroud)
最后,我们在我们的超类约束中使用这个封闭类型的家族Example.
class Instance MemberOfExample a ~ Ok => Example a where
method :: a -> a
Run Code Online (Sandbox Code Playgroud)
我们可以定义有效的情况下,对Int与Integer往常一样.
instance Example Int where
method x = x + 1
instance Example Integer where
method x = x + 1
Run Code Online (Sandbox Code Playgroud)
但是,我们不能对其他类型比定义无效的情况下Int和Integer.
-- GHC error: Couldn't match type `Instance MemberOfExample Float' with `Ok'
instance Example Float where
method x = x + 1
Run Code Online (Sandbox Code Playgroud)
而且我们也无法扩展有效类型集.
-- GHC error: Duplicate instance declarations
instance Closed MemberOfExample where
type Instance MemberOfExample Float = Ok
-- GHC error: Associated type `Instance' must be inside a class instance
type instance Instance MemberOfExample Float = Ok
Run Code Online (Sandbox Code Playgroud)
不幸的是,我们可以编写以下虚假实例:
-- Unfortunately accepted
instance Instance MemberOfExample Float ~ Ok => Example Float where
method x = x + 1
Run Code Online (Sandbox Code Playgroud)
但由于我们永远无法履行平等约束,我认为我们不能将其用于任何事情.例如,拒绝以下内容:
-- Couldn't match type `Instance MemberOfExample Float' with `Ok'
test = method (pi :: Float)
Run Code Online (Sandbox Code Playgroud)
您可以将类型类重构为数据声明(使用记录语法),其中包含类型类所具有的所有函数.一个固定的有限实例列表听起来像你不需要一个类.
这当然是编译器正在做的事情,无论如何都要禁止你的课程.
这将允许您将实例列表作为函数导出到数据类型,并且可以导出它们,但不能导出数据类型的构造函数.同样,您可以限制导出器功能的导出,只需导出您真正想要的接口.
这很好用,因为数据类型不受类型类的模块边界交叉开放世界假设的影响.
有时添加类型系统复杂性会让事情变得更难.
当您对拥有一组枚举实例感兴趣时,这个技巧可能会有所帮助:
class (Elem t '[Int, Integer, Bool] ~ True) => Closed t where
type family Elem (t :: k) (ts :: [k]) :: Bool where
Elem a '[] = False
Elem a (a ': as) = True
Elem a (b ': bs) = Elem a bs
instance Closed Int
instance Closed Integer
-- instance Closed Float -- ERROR
Run Code Online (Sandbox Code Playgroud)