让我们玩一下编译器:我们有一个类型(f a)
我们想看看它是否是一个有效的满足Rows
约束。为此,我们需要发送一份证明(f a)
满足Show (f a)
以及。这不是问题,除非有人写了
instance Rows (f a) => Show (f a) where ...
在这种情况下,我又回到了开始的地方。像这样编码一个无限循环有点愚蠢,但 Haskell 静态地确保它实际上是不可能的,除非你要求UndecidableInstances
.
通常,Haskell 确保“解析追踪”的每一步都会将类型的大小减少至少 1 个构造函数。这通过结构归纳得出了一个非常简单的证明,即我们最终会在有限数量的分辨率中得到裸类型。
这是过度限制性的,显然一些实例解析步骤是有意义的、有用的和总体的,即使它们不会立即减少构造函数树。同样的总体性限制也适用于 Agda 和 Coq 等语言,并且将算法操纵为通过结构归纳进行的算法通常是一个说明性的挑战。
那么我们该如何解决呢?一种方法是失去Show
的约束class
定义使用像这样的实例Show1
from 前奏曲 http://hackage.haskell.org/packages/archive/prelude-extras/0.3/doc/html/Prelude-Extras.html.
class Show1 f where ...
show1 :: (Show1 f, Show a) => f a -> String -- not an instance definition!
然后有instance (Foldable f, Show1 f, Show a) => Rows (f a) where ...
这在我的测试中有效。然后,您可以将默认实例编写为独立函数。
defRRepr :: Show a => a -> [ByteString]
defRRepr = (:[]) . pack . show
并在为 a 编写实例定义时使用它Show
能干的事。
另一种解决方案是使用newtype
包装器允许 Haskell 看到解析的“层”已被删除。
instance (Foldable f, Show (f a)) => Rows (FoldableRow f a) where
rRepr = const [] . unFR
newtype FoldableRow f a = FR { unFR :: f a } deriving (Show)