假设我有这样的数据类型:
{-# LANGUAGE RankNTypes #-}
data X a = forall b. Show b => X (a b)
我想派生Show (X a)
,但当然,如果有Show (a b)
的实例,我只能这样做。我很想写
{-# LANGUAGE StandaloneDeriving #-}
deriving instance Show (a b) => Show (X a)
但遗憾的是,类型变量b
在实例上下文中不可用,因为它受到了forall的约束。
我的下一次尝试是将Show (a b)
上下文移动到数据类型定义中的forall中,如下所示:
data X a = forall b. Show (a b) => X (a b)
deriving instance Show (X a)
这编译,但不幸的是现在我已经失去了构建X
的能力(a b)
的能力。
有没有办法允许X
与任何(a b)
一起构建,然后仅在Show (X a)
可见时有条件地推导(a b)
?
答案 0 :(得分:7)
这是Prelude课程中的一个缺陷。虽然体现在prelude-extras
包中,但还是有很好的解决方法。我将在下面概述。
我们想要创建一个更高级的Show
课程。看起来像这样
class Show1 a where
show1 :: Show b => a b -> String
然后我们至少可以准确地表达我们想要的约束,如
deriving instance Show1 a => Show (X a)
不幸的是,编译器还没有足够的信息来实现这种推导。我们需要证明(Show b, Show1 a)
足以导出Show (a b)
。要做到这一点,我们需要启用一些(可怕的,但是使用得当的)扩展
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverlappingInstances #-}
instance (Show b, Show1 a) => Show (a b) where
show = show1
现在我们有了这个证明,编译器将能够得到我们需要的东西
data X a = forall b . Show b => X (a b)
deriving instance Show1 a => Show (X a)
答案 1 :(得分:5)
我会采用类似但略有不同的方法来回答J. Abrahamson的答案。
您要求的确切要求无法完成,因为类型类需要静态解析,但Show (a b)
的存在可能动态取决于b
的瞬间。此实例化隐藏在X
值内,因此当您只有X b
未知来源时,类型检查器不可见。
当a
出现Show (a b)
之类的Show b
上写条件会很好,因为Show (a b)
的存在实际上并不依赖于b
Show b
我们已经知道{-# LANGUAGE GADTs #-}
data ShowDict a where
ShowDict :: Show a => ShowDict a
始终是真的。
我们无法直接写出这个条件,但我们可以使用GADT表达类似的内容:
ShowDict a
Show a
类型提供了Show1
类的一种具体化 - 它是我们可以传递的东西并定义了函数。
我们现在可以定义一个Show (a b)
类,只要我们有Show b
,就会表达条件class Show1 a where
show1Dict :: ShowDict b -> ShowDict (a b)
:
Show (X a)
现在,我们可以Show1
定义ShowDict (a b)
,构建Show
,然后对其进行模式匹配,以显示{-# LANGUAGE ScopedTypeVariables #-}
instance Show1 a => Show (X a) where
show (X (v :: a b)) =
case show1Dict ShowDict :: ShowDict (a b) of
ShowDict -> "X (" ++ show v ++ ")"
实例:
Show
更完整的实施还包括showsPrec
(showList
和Show1
)的其他成员。
这个解决方案的好处在于我们可以轻松地为[]
定义Show
,自动重用基础instance Show1 [] where
show1Dict ShowDict = ShowDict
实例:
Show (a b)
我也更喜欢避免J. Abrahamson的答案中非常通用的Show
实例,但是必须将逻辑放在X
{{1}}实例中是我们最终必须手动实现它而不是获取构造函数的自动派生行为。