封闭式课程

J. *_*son 17 haskell types static-analysis typeclass

是否有可能创建一个不再允许新成员的类型类(可能通过使用模块边界)?我可以拒绝导出完整实例定义所需的函数,但如果有人生成无效实例,则只会导致运行时错误.我可以把它编成错误吗?

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

您可以通过封闭类型族编码封闭类型类,这些类可以依次编码为关联类型族.此解决方案的关键是关联类型系列的实例位于类型类实例中,并且每个单形类型只能有一个类型类实例.

请注意,此方法独立于模块系统.我们提供了一个明确的列表,列出了哪些实例是合法的,而不是依赖于模块边界.这意味着,一方面,法律实例可以分布在多个模块甚至包上,另一方面,即使在同一模块中也不能提供非法实例.

对于这样的回答,我认为我们要关闭下面的类,使其只能被实例化的类型IntInteger,但不支持其他类型的:

 -- 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来编码IntIntegerOk,和所有其他类型都没有.

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)

我们可以定义有效的情况下,对IntInteger往常一样.

instance Example Int where
  method x = x + 1

instance Example Integer where
  method x = x + 1
Run Code Online (Sandbox Code Playgroud)

但是,我们不能对其他类型比定义无效的情况下IntInteger.

-- 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)


And*_*ewC 7

您可以将类型类重构为数据声明(使用记录语法),其中包含类型类所具有的所有函数.一个固定的有限实例列表听起来像你不需要一个类.

这当然是编译器正在做的事情,无论如何都要禁止你的课程.

这将允许您将实例列表作为函数导出到数据类型,并且可以导出它们,但不能导出数据类型的构造函数.同样,您可以限制导出器功能的导出,只需导出您真正想要的接口.

这很好用,因为数据类型不受类型类的模块边界交叉开放世界假设的影响.

有时添加类型系统复杂性会让事情变得更难.


hei*_*bug 5

当您对拥有一组枚举实例感兴趣时,这个技巧可能会有所帮助:

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)