我想定义一个Haskell模块,该模块为各种类型导出类C
和C
的实例。因为我想定义很多实例,这些实例可以按照给定的方案自动定义,所以我定义了一个助手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
中
如果我把defineCInstance
和C
都移到Foo.Internal
中,defineCInstance
可以很容易地引用C
,而我可以毫无问题地将defineCInstance
拼接到Foo
中。但是,Foo
中定义的实例将不再与C
定义在同一模块中,从而成为孤立实例。我希望避免这种情况,因为这样我就必须对孤立实例的警告保持沉默,而AFAIK仅适用于整个文件,并且可能对同一文件中的其他意外孤立实例的警报保持沉默。我还注意到,haddock在文档中将所有实例都列为孤立实例,我也希望避免这样做。
解决方案2,不要让defineCInstance
直接引用C
我可以使用类似instance $(conT $ mkName "C")
的内容来代替defineCInstance
中的instance C
。然而,这对编译器隐藏了C
和defineCInstance
之间的依赖关系,这使得出错的可能性更大。例如,如果我重命名C
,但忘记更改defineCInstance
中的名称,编译器仍然会愉快地编译defineCInstance
。更糟糕的是,它依赖于在defineCInstance
的剪接位点的范围内的正确的C
。如果用户的作用域中有错误的C
,则生成的代码将完全没有意义。
是否有某种方法可以定义C
、defineCInstance
和C
的实例,仍然允许直接从defineCInstance
引用C
,并且避免定义孤立实例?
-
定义一个内部
defineCInstance_
,以C
的Name
为参数; -
在包含类的外部
Foo
模块中,标准实例使用defineCInstance_ ''Foo.C
; -
导出
defineCInstance = defineCInstance_ ''Foo.C
,防止用户使用错误的名称。
在步骤2中,您可以将所有相关类型放在列表中,并一次遍历所有类型,因此您只需引用''Foo.C
名称两次:一次在拼接中,一次在导出的defineCInstance
中。