量化约束与(封闭)类型族的比较

时间:2019-06-06 03:38:25

标签: haskell type-families deriving quantified-constraints

我正在尝试将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)

2 个答案:

答案 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的字段类型,因为它们可能会更改。

    < / li>

不幸的是,该约束实际上不是格式正确的。以下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。您应该想象,在这一点上,约束求解器不知道ShowShowHKD之间的任何关系。它只需要一个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)