用自己的证明丰富约束求解器

时间:2018-07-15 22:09:55

标签: haskell ghc type-families

我有以下类型族:

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DataKinds #-}

data Nat = Z | S Nat
type family WrapMaybes (n :: Nat) (a :: *) :: *
type instance WrapMaybes Z a = a
type instance WrapMaybes (S n) a = Maybe (WrapMaybes n a)

这通常可以按预期运行,例如WrapMaybes (S (S Z)) Int ~ Maybe (Maybe Int)

现在,显然(嗯,也许是出于终止的原因?!)以下通勤身份成立:WrapMaybes n (Maybe a) ~ Maybe (WrapMaybes n a)

GHC本身无法推断该属性,因此,我正在寻找添加该公理的方法,最好是通过一些补充证明。到目前为止,我想出的最好的方法是coincident overlap in type families。但是建议的语法似乎不再起作用(type instance where会触发语法错误),因此我只使用了这一点:

type instance WrapMaybes n (Maybe a) = Maybe (WrapMaybes n a)

但这使GHC再次抱怨:

Conflicting family instance declarations:
  WrapMaybes 'Z a = a
  WrapMaybes n (Maybe a) = Maybe (WrapMaybes n a)

Conflicting family instance declarations:
  WrapMaybes ('S n) a = Maybe (WrapMaybes n a)
  WrapMaybes n (Maybe a) = Maybe (WrapMaybes n a)

所以:

  1. 是否有办法使建议的机制起作用?例如。如何摆脱语法错误?
  2. GHC Haskell中重合还是重叠吗?
  3. 还有什么其他机制可以向GHC教授必要的公理?

1 个答案:

答案 0 :(得分:3)

GHC Haskell确实仍然存在重合类型的家庭重叠,如here所述。 GHC 8.4.3仍然接受文档中的示例以及您链接到的博客文章。


但是,我不认为巧合的重叠对您有帮助,因为根据GHC使用的语法相等性检查,RHS不相等(不相等)。基本上,对于巧合的重叠类型家庭观念行之有效,GHC已经必须知道您要“证明”的财产。

要实际证明这一点,每当需要使用此事实时,都需要将想要的类型相等性引入到类型化环境中。一种方法是使用:~:中的Data.Type.Equality

data a :~: b where  -- See Note [The equality types story] in TysPrim
  Refl :: a :~: a

这里的基本思想是,当您使用a :~: b构造函数创建类型为Refl的值时,GHC必须知道a ~ b。稍后在此Refl构造函数上进行模式匹配时,您会将这种相等性重新引入到GHC的键入环境中。您可以使用它来建立归纳证明。

但是,为了能够建立归纳证明,您将需要能够基于Nat的值进行分支,而data SNat (n :: Nat) where SZ :: SNat 'Z SS :: SNat n -> SNat ('S n) 的值完全在类型级别上是无法做到的。为了解决这个问题,我们可以引入一个“单一” GADT:

SNat

当对类型n的值进行模式匹配时,由于wrapMaybeComm' :: forall n a. SNat n -> WrapMaybes n (Maybe a) :~: Maybe (WrapMaybes n a) 类型的GADT结构,您将在类型环境中引入有关类型级自然值的信息变量。

这意味着我们可以编写具有以下类型的函数:

n

这里的想法是,如果给它一个(值级别的见证人)一个类型级别的自然WrapMaybes n (Maybe a),它将返回一个见证人Maybe (WrapMaybes n a)与以下事实相同: wrapMaybeComm'。当您对该证人进行模式匹配时,GHC将确信该事实是真实的,并能够使用它。

我们现在可以为0编写一个定义,看起来非常像是必要事实的归纳证明。基本情况是wrapMaybeComm' SZ = Refl

n = 0

Maybe a ~ Maybe a时,GHC可以立即看到该wrapMaybeComm'

在归纳情况下,我们需要致电wrapMaybeComm' (SS m) = case wrapMaybeComm' @_ @a m of Refl -> Refl

Refl

WrapMaybes m (Maybe a) ~ Maybe (WrapMaybes m a)上的模式匹配告诉GHC n ~ 'S m,其中 WrapMaybes n (Maybe a) ~ WrapMaybes ('S m) (Maybe a) {- defn. of m -} ~ Maybe (WrapMaybes m (Maybe a)) {- defn. of WrapMaybes -} ~ Maybe (Maybe (WrapMaybes m (Maybe a))) {- IH -} ~ Maybe (WrapMaybes ('S m) (Maybe a)) {- defn. of WrapMaybes -} ~ Maybe (WrapMaybes n (Maybe a)) {- defn of m -} 。这样,GHC可以看到

Refl

因此知道右侧的SNat会进行类型检查。


如果您不想在各处随身携带KnownNat,可以通过class KnownNat (n :: Nat) where getSNat :: SNat n instance KnownNat 'Z where getSNat = SZ instance KnownNat n => KnownNat ('S n) where getSNat = SS getSNat wrapMaybeComm :: forall n a. (KnownNat n) => WrapMaybes n (Maybe a) :~: Maybe (WrapMaybes n a) wrapMaybeComm = wrapMaybeComm' @n @a getSNat 类的定义将它们替换为(有时更安静)typeclass字典,如下所示:< / p>

e

要实际使用该定理,每当您有一个表达式n时,GHC因为不知道与acase wrapMaybeComm @n @a of Refl -> e的相等性而拒绝进行类型检查,则可以改写-XGADTs,它应该可以工作。


该方法可用于向GHC教授各种有趣的归纳事实。在一般情况下,GHC当然不能知道所有相等的类型,因为这将要求GHC能够确定功能非常强大的逻辑系统的任意定理,而这是不可能的。但是,有趣的归纳定理的许多证明都可以很容易地转换成这种样式,在依赖类型的语言中,这种变体(无需额外的单例工作)很常见。


旁注:要使用上述功能,您将需要一些额外的GHC Haskell扩展。

  • SNat n :SNat单例必须是GADT,以确保每个n都有一个值(特别是SS { {1}}个应用于SZ)。
  • -XScopedTypeVariables :这是确保对证明函数的调用使用正确的类型所必需的。
  • -XTypeOperators :给定代码使用:~:中的Data.Type.Equality,它是类型运算符。但是,可以使用不是类型运算符的等效定义(例如Equal a b而不是a :~: b)。
  • -XAllowAmbiguousTypes :给定的定义具有GHC所谓的歧义类型(这些函数需要额外的类型签名和/或可见的类型应用程序才能消除某些tyvar的歧义)。可以通过使用更多Proxy参数来解决此问题。
  • -XTypeApplications :此(@tyvar语法)用于方便地指定类型变量。但是,应该可以用(有些烦人/冗长的)显式类型注释代替。