使用模板haskell定义实例时避免孤立实例



我想定义一个Haskell模块,该模块为各种类型导出类CC的实例。因为我想定义很多实例,这些实例可以按照给定的方案自动定义,所以我定义了一个助手TH函数来定义实例,如下所示:

module Foo (C) where
class C a
defineCInstance :: TypeQ -> DecsQ
defineCInstance t =
[d|
instance C $t
|]
defineCInstance [t| () |]
defineCInstance [t| Char |]
defineCInstance [t| Int |]

然而,上面的例子没有编译,因为由于阶段限制,不允许在定义它的同一模块的拼接中使用defineCInstance。这可以通过将defineCInstance移动到一个新模块(我们称之为Foo.Internal(并在Foo中导入来解决。然而,当我只是将defineCInstance移到另一个模块中时,它的作用域中不会有C,我目前看到了两种解决方法。

解决方案1,将C也移到Foo.Internal

如果我把defineCInstanceC都移到Foo.Internal中,defineCInstance可以很容易地引用C,而我可以毫无问题地将defineCInstance拼接到Foo中。但是,Foo中定义的实例将不再与C定义在同一模块中,从而成为孤立实例。我希望避免这种情况,因为这样我就必须对孤立实例的警告保持沉默,而AFAIK仅适用于整个文件,并且可能对同一文件中的其他意外孤立实例的警报保持沉默。我还注意到,haddock在文档中将所有实例都列为孤立实例,我也希望避免这样做。

解决方案2,不要让defineCInstance直接引用C

我可以使用类似instance $(conT $ mkName "C")的内容来代替defineCInstance中的instance C。然而,这对编译器隐藏了CdefineCInstance之间的依赖关系,这使得出错的可能性更大。例如,如果我重命名C,但忘记更改defineCInstance中的名称,编译器仍然会愉快地编译defineCInstance。更糟糕的是,它依赖于在defineCInstance的剪接位点的范围内的正确的C。如果用户的作用域中有错误的C,则生成的代码将完全没有意义。

是否有某种方法可以定义CdefineCInstanceC的实例,仍然允许直接从defineCInstance引用C,并且避免定义孤立实例?

  1. 定义一个内部defineCInstance_,以CName为参数;

  2. 在包含类的外部Foo模块中,标准实例使用defineCInstance_ ''Foo.C

  3. 导出defineCInstance = defineCInstance_ ''Foo.C,防止用户使用错误的名称。

在步骤2中,您可以将所有相关类型放在列表中,并一次遍历所有类型,因此您只需引用''Foo.C名称两次:一次在拼接中,一次在导出的defineCInstance中。

最新更新