我正在尝试将this blogpost's approach to higher-kinded data without dangling Identity
functors for the trival case与量化约束推导一起使用:
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE QuantifiedConstraints, StandaloneDeriving, UndecidableInstances #-}
module HKD2 where
import Control.Monad.Identity
type family HKD f a where
HKD Identity a = a
HKD f a = f a
data Result f = MkResult
{ foo :: HKD f Int
, bar :: HKD f Bool
}
deriving instance (forall a. Show a => Show (HKD f a)) => Show (Result f)
这会导致令人反感的错误消息:
无法推断
Show (HKD f a)
从上下文中:forall a. Show a => Show (HKD f a)
有没有办法做到这一点而又不费吹灰之力
deriving instance (Show (HKD f Int), Show (HKD f Bool)) => Show (Result f)
?
答案 0 :(得分:7)
tl; dr,要旨:https://gist.github.com/Lysxia/7f955fe5f2024529ba691785a0fe4439
首先,如果问题在于避免重复代码,则大多数情况下仅使用泛型即可解决,而无需使用QuantifiedConstraints
。约束(Show (HKD f Int), Show (HKD f Bool))
可以从通用表示Rep (Result f)
计算得出。通用数据包(免责声明:我写的)实现了这一点:
data Result f = MkResult (HKD f Int) (HKD f Bool)
deriving Generic
-- GShow0 and gshowsPrec from Generic.Data
instance GShow0 (Rep (Result f)) => Show (Result f) where
showsPrec = gshowsPrec
或使用DerivingVia
:
-- Generically and GShow0 from Generic.Data
deriving via Generically (Result f) instance GShow0 (Rep (Result f)) => Show (Result f)
尽管如此,由于各种原因,约束(Show (HKD f Int), Show (HKD f Bool))
可能不理想。 QuantifiedConstraints
扩展名似乎提供了更自然的约束forall x. Show (HKD f x)
:
它将包含元组(Show (HKD f Int), Show (HKD f Bool))
;
与该元组相反,当记录变大时,它的大小不会爆炸,并且不会泄漏Result
的字段类型,因为它们可能会更改。
不幸的是,该约束实际上不是格式正确的。以下GHC问题详细讨论了该问题:https://gitlab.haskell.org/ghc/ghc/issues/14840我尚不了解所有原因,但简要来说:
在可预见的将来,由于理论和实践上的原因,量化约束将无法直接与类型族配合使用;
但是对于大多数用例,都有一种解决方法。
量化约束应视为一种“局部instance
”。然后,一般规则是,在任何实例的头中都不允许使用类型族(“实例头” =后HEAD
中的instance ... => HEAD where
)。因此forall a. Show (HKD f a)
(被视为“本地” instance Show (HKD f a)
)是非法的。
以下解决方案归功于Icelandjack(来源:this comment from the ticket linked earlier;也感谢Ryan Scott的转发。)
我们可以定义另一个等效于Show (HKD f a)
的类:
class Show (HKD f a) => ShowHKD f a
instance Show (HKD f a) => ShowHKD f a
现在forall x. ShowHKD f x
是一种法律约束,可以从道德上表达预期的forall x. Show (HKD f x)
。但是如何使用它一点也不明显。例如,以下代码片段无法进行类型检查(注意:我们可以轻松忽略歧义问题):
showHKD :: forall f. (forall x. ShowHKD f x) => HKD f Int -> String
showHKD = show
-- Error:
-- Could not deduce (Show (HKD f Int)) from the context (forall x. ShowHKD f x)
这是违反直觉的,因为ShowHKD f x
等效于Show (HKD f x)
,当然可以用Show (HKD f Int)
实例化。那为什么拒绝呢?约束求解器的原因向后:使用show
首先需要约束Show (HKD f Int)
,但是求解器会立即卡住。它在上下文中看到forall x. ShowHKD f x
,但是求解器不知道应该将x
实例化为Int
。您应该想象,在这一点上,约束求解器不知道Show
和ShowHKD
之间的任何关系。它只需要一个Show
约束,并且上下文中没有约束。
通过使用ShowHKD f x
(此处为ShowHKD f Int
)所需的实例化注释函数的主体,我们可以如下帮助约束求解器:
showHKD :: forall f. (forall x. ShowHKD f x) => HKD f Int -> String
showHKD = show :: ShowHKD f Int => HKD f Int -> String
此注释为主体ShowHKD f Int
提供约束show
,从而使超类可用Show (HKD f Int)
,因此show
可以立即使用满意。另一方面,注释需要从其上下文提供约束ShowHKD f Int
,约束forall x. ShowHKD f x
。这些约束 match ,导致约束求解器适当地实例化x
。
有了这个,我们可以使用量化约束来实现Show
,使用泛型来填充主体,并使用一些注释来实例化量化约束,
(ShowHKD f Int, ShowHKD f Bool)
:
instance (forall a. Show a => ShowHKD f a) => Show (Result f) where
showsPrec = gshowsPrec :: (ShowHKD f Int, ShowHKD f Bool) => Int -> Result f -> ShowS
像以前一样,这些约束可以使用泛型自动实现,因此在此实现中从一种类型更改为另一种类型的唯一更改是名称Result
:
instance (forall a. Show a => ShowHKD f a) => Show (Result f) where
showsPrec = gshowsPrec :: ShowHKDFields f (Rep (Result HKDTag)) => Int -> Result f -> ShowS
-- New definitions: ShowHKDFields and HKDTag; see gist at the end.
付出更多努力,我们也可以拥有DerivingVia
:
deriving via GenericallyHKD Result f instance (forall a. Show a => ShowHKD f a) => Show (Result f)
-- New definition: GenericallyHKD; see gist.
要点:https://gist.github.com/Lysxia/7f955fe5f2024529ba691785a0fe4439
答案 1 :(得分:3)
我认为您无法做到这一点,但我肯定是错的。在您的示例中,您缺少一个额外的约束Show (f a)
以使其完整:
deriving instance (forall a. (Show a, Show (f a)) =>
Show (HKD f a)) => Show (Result f)
但这意味着Show
的{{1}}实例不能依赖f a
,这对于特定的a
可能是正确的,但一般而言并非如此。
修改
但是与此同时,可以不用f
来编写类似的内容:
TypeFamilies
所以,我不确定GHC为什么无法弄清楚。
编辑2
这是一个有趣的观察,它汇编如下:
data Bar f = MkBar (f Int)
deriving instance (forall a . Show a => Show (f a)) => Show (Bar f)
并按预期工作:
type family HKD f a where
-- HKD Identity a = a
HKD f Int = Int
HKD f a = f a
data Result f = MkResult
{ foo :: HKD f Int
, bar :: HKD f Bool
}
deriving instance (forall a. Show a => Show (f a)) => Show (Result f)
因此,看起来λ> show $ MkResult 5 (Just True)
"MkResult {foo = 5, bar = Just True}"
上的匹配以某种方式弄乱了类型检查器。
值得注意的是,即使对于简化示例,限制为f
也会产生与问题相同的编译时错误:
Show (HDK f a)