最近,我问了一个关于我创建的生成运行时无限循环的实例的问题,我得到了一个很好的答案!既然我明白了发生了什么,我有一个新问题:我可以修正我的尝试来实现我最初的目标吗?
让我重申并具体澄清我的问题是什么:我想创建一个类型类,用于在我的代码中的一些等效数据类型之间进行转换。我创建的类型类既非常简单又非常通用:它包含一个在任意数据类型之间进行转换的转换函数:
class Convert a b where
convert :: a -> b
但是,此类型类有一个特定目的:在具有规范表示的特定值类之间进行转换。因此,有一个特定的数据类型是“规范的”,我想使用这个数据类型的属性来减轻我的类型类实现者的负担。
data Canonical = ...
class ConvertRep a b where
convertRep :: a -> b
具体来说,考虑两种不同的表示,RepA
和RepB
。我可能会合理地定义一些实例来将这些表示转换为Canonical
:
instance ConvertRep RepA Canonical where ...
instance ConvertRep Canonical RepA where ...
instance ConvertRep RepB Canonical where ...
instance ConvertRep Canonical RepB where ...
现在,这立即有用,因为我现在可以同时使用convertRep
这两种表示形式,但它主要只是作为重载convertRep
名称的一种方式。我想做一些更强大的事情:毕竟,我现在已经有效地定义了以下类型的四个函数:
RepA -> Canonical
Canonical -> RepA
RepB -> Canonical
Canonical -> RepB
在我看来,根据这些定义,我还应该能够生成以下类型的两个函数:
RepA -> RepB
RepB -> RepA
基本上,由于这两种数据类型都可以转换为/从规范表示,我想直接自动生成相互之间的转换函数。正如我在上述问题中提到的那样,我的尝试看起来像这样:
instance (ConvertRep a Canonical, ConvertRep Canonical b) => ConvertRep a b where
convertRep = convertRep . (convertRep :: a -> Canonical)
不幸的是,这个实例过于宽松,当提供两种我认为应该无效的类型时,它会导致生成的代码递归——我还没有定义规范转换的类型。
为了尝试解决这个问题,我考虑了另一种更简单的方法。我想我可以使用两个类型类而不是一个来防止这个递归问题:
class ToCanonical a where
toCanonical :: a -> Canonical
class FromCanonical a where
fromCanonical :: Canonical -> a
现在可以定义一个新函数来执行convertRep
我最初感兴趣的转换:
convertRep :: (ToCanonical a, FromCanonical b) => a -> b
convertRep = fromCanonical . toCanonical
然而,这是以灵活性为代价的:不再可能在两个非规范表示之间创建直接转换实例。
例如,也许我知道这一点RepA
并且RepB
会非常频繁地互换使用,因此它们会在彼此之间进行相当多的转换。因此,转换为/从的额外步骤Canonical
是浪费时间。我想选择定义一个直接转换实例:
instance ConvertRep RepA RepB where
convertRep = ...
它提供了两种常见类型之间的“快速路径”转换。
总结一下:有没有办法使用 Haskell 的类型系统来完成所有这些目标?
- 给定在表示和规范形式之间转换的函数,在表示之间“生成”转换。
- 可选择在通常转换的实例之间提供“快速路径”。
- 拒绝没有明确定义的实例;也就是说,不允许
ConvertRep Canonical Canonical
(和类似的)创建无限重复并产生底部的实例。
Haskell 类型系统令人印象深刻,但我担心在这种情况下,它的实例解析规则不足以同时完成所有这些目标。