众所周知,实现Haskell类型类的一种方法是通过'类型类词典' . (这当然是ghc中的实现,尽管我强制要求其他实现是可能的 . )为了解决这个问题,我将简要介绍一下这是如何工作的 . 类声明就像
class (MyClass t) where
test1 :: t -> t -> t
test2 :: t -> String
test3 :: t
可以机械地转换为数据类型的定义,如:
data MyClass_ t = MyClass_ {
test1_ :: t -> t -> t,
test2_ :: t -> String,
test3_ :: t,
}
然后我们可以将每个实例声明机械地转换为该类型的对象;例如:
instance (MyClass Int) where
test1 = (+)
test2 = show
test3 = 3
变成
instance_MyClass_Int :: MyClass_ Int
instance_MyClass_Int = MyClass_ (+) show 3
类似地,具有类型类约束的函数可以变成一个带有额外参数的函数;例如:
my_function :: (MyClass t) => t -> String
my_function val = test2 . test1 test3
变成
my_function_ :: MyClass_ t -> t -> String
my_function_ dict val = (test2_ dict) . (test1_ dict) (test3_ dict)
关键是,只要编译器知道如何填写这些隐藏的参数(这并非完全无关紧要),那么您可以将使用类和实例的代码转换为仅使用该语言的更多基本功能的代码 .
有了这样的背景,这是我的问题 . 我有一个模块 M
,它定义了一堆具有类约束的类和函数 . M
是'opaque';我可以看到它导出的内容(相当于.hi文件),我可以从中导入,但我看不到它的源代码 . 我想构建一个新的模块 N
,它基本上导出相同的东西,但上面应用了转换 . 例如,如果 M
已导出
class (Foo t) where
example1 :: t -> t -> t
example2 :: t -- note names and type signatures visible here
-- because they form part of the interface...
instance (Foo String) -- details of implementation invisible
instance (Foo Bool) -- details of implementation invisible
my_fn :: (Foo t) => t -> t -- exported polymorphic fn with class constraint
-- details of implementation invisible
N
会开始的
module N where
import M
data Foo_ t = Foo_ {example1_ :: t-> t -> t, example2_ :: t}
instance_Foo_String :: Foo_ String
instance_Foo_String = Foo_ example1 example2
instance_Foo_Bool :: Foo_ Bool
instance_Foo_Bool = Foo_ example1 example2
my_fn_ :: Foo_ t -> t -> t
my_fn_ = ???
我的问题是 what on earth I can put in place of the ??? . 换句话说,我可以编写什么来从原始函数中提取函数 my_fn
的'explicit typeclass'版本?它看起来相当棘手,并且它在引擎盖下'模块M基本上已经输出了像我想要创建的 my_fn_
这样的东西 . (或者至少,它是在GHC上 . )
3 回答
为了记录,我想我会解释我已经知道的“hacky”解决方案 . 我将基本上使用一系列示例来说明它 . 因此,让我们假设我们正在尝试在下面对类,实例和函数进行实现(主要包括非常标准的类型类,通常在某种程度上简化了说明):
在这些例子中有一些普遍性:我们有
'a'在 class 成员中处于积极地位
'a'在 class 成员中处于负面位置
需要灵活实例的实例
更高级的类型
我们将多参数类型的家庭作为练习!请注意,我确实相信我所呈现的是一个完全通用的句法程序;我只是认为通过实例说明比通过正式描述转换更容易 . 无论如何,我们假设我们有以下功能要处理:
然后实际的具体化看起来像:
请注意,我们使用了很多
unsafeCoerce
,但始终关联两种类型,这些类型仅在存在newtype时有所不同 . 由于运行时表示是相同的,这是可以的 .你似乎要求的东西被称为“本地实例” . 这意味着你可以写下这样的东西:
本地实例是类型类的自然扩展 . 它们甚至是Wadler和Blott的论文“如何使ad hoc多态性不那么特别”的形式主义的标准 . 但是,它们存在问题,因为它们破坏了称为主要类型的属性 . 此外,它们还可以打破以下假设:对于特定类型,只有一个特定约束的实例(例如,Data.Map关于Ord实例的假设) . 第一个问题可以通过在本地实例中要求额外的类型注释来解决,后者与有争议的“孤立实例”相关,这会导致类似的问题 .
另一篇相关论文是Kiselyov和Shan的“功能珍珠:隐式配置”,其中包含各种类型系统技巧来模拟本地类型实例,尽管它并不真正适用于您的情况(预先存在的类型类)IIRC .
This isn't a solution in general, but only for some special cases.
对于类型参数
t
出现在其类型中的负位置的class C t
的类方法,有一种hacky方法 . 例如,example1 :: Foo t => t -> t -> t
没问题,但不是example2 :: Foo t => t
.诀窍是创建一个包装器数据类型
Wrapper t
,它包含t
上的显式字典方法t
值,并且具有利用适当的包装字典方法的Foo
实例,例如有些东西告诉我这可能不是你正在寻找的解决方案 - 它不是通用的 . 在这里的例子中,我们不能对
example2
做任何事情,因为它没有t
的负面出现,"sneak"函数在里面 . 对于您的示例,这意味着模块M
中的my_fn
只能使用example1
.