一般类型的"引理"功能被理解?

时间:2015-05-13 20:40:51

标签: haskell theorem-proving dependent-type higher-rank-types

也许这是一个愚蠢的问题。这是the Hasochism paper的引用:

  

解决这个问题的一种方法是对lemma进行编码   参数化方程,如Haskell函数。一般来说,这样的外阴   可以编码为类型的函数:

∀ x1 ... xn. Natty x1 → ... → Natty xn → ((l ~ r) ⇒ t) → t

我以为我理解RankNTypes,但我无法理解这个命题的最后部分。我非正式地阅读它,并给出一个需要l ~ r的术语,然后返回该术语"。我确信这种解释是错误的,因为它似乎导致了循环:在证明本身结束之前我们不知道l ~ r,所以我怎么能期望作为假设提供证明一个要求的术语?

我本来期望一个等式证明有一个更像这样的类型:

Natty x1 → ... → Natty xn → l :~: r

非正式地,#34;给出一堆Natty s,返回lr相等的命题的证明" (使用GHC' Data.Type.Equality)。这对我来说更有意义,并且似乎与你在其他依赖类型的系统中所说的一致。我猜这相当于论文中的版本,但我很难在心理上将这两个版本放在一边。

简而言之,我很困惑。我觉得我错过了一个关键的洞察力。我应该如何阅读((l ~ r) => t) -> t类型?

3 个答案:

答案 0 :(得分:6)

  

我将其视为"给定一个需要l ~ r的术语,请将其返回   术语#34;

它给出了一个类型包含l的术语,返回该术语,其中所有lr s替换为类型" (或在另一个方向r -> l)。这是一个非常巧妙的技巧,允许您将所有congtranssubst和类似内容委托给GHC。

以下是一个例子:

{-# LANGUAGE GADTs, DataKinds, PolyKinds, TypeFamilies, TypeOperators, RankNTypes #-}

data Nat = Z | S Nat

data Natty n where
    Zy :: Natty Z
    Sy :: Natty n -> Natty (S n)

data Vec a n where
    Nil  :: Vec a Z
    Cons :: a -> Vec a n -> Vec a (S n)

type family (n :: Nat) :+ (m :: Nat) :: Nat where
    Z     :+ m = m
    (S n) :+ m = S (n :+ m)

assoc :: Natty n -> Natty m -> Natty p -> (((n :+ m) :+ p) ~ (n :+ (m :+ p)) => t) -> t
assoc  Zy     my py t = t
assoc (Sy ny) my py t = assoc ny my py t

coerce :: Natty n -> Natty m -> Natty p -> Vec a ((n :+ m) :+ p) -> Vec a (n :+ (m :+ p))
coerce ny my py xs = assoc ny my py xs

<强>更新

专业化assoc

具有指导意义
assoc' :: Natty n -> Natty m -> Natty p ->
            (((n :+ m) :+ p) ~ (n :+ (m :+ p)) => Vec a (n :+ (m :+ p)))
                                               -> Vec a (n :+ (m :+ p))
assoc'  Zy     my py t = t
assoc' (Sy ny) my py t = assoc ny my py t

coerce' :: Natty n -> Natty m -> Natty p -> Vec a ((n :+ m) :+ p) -> Vec a (n :+ (m :+ p))
coerce' ny my py xs = assoc' ny my py xs

Daniel Wagner 解释了评论中发生了什么:

  

或者,换句话说,你可以阅读((l~r)=&gt; t) - &gt; t as,&#34;给出   一个很好的类型的术语,假设l~r,返回相同的术语   从我们已经证明l~r并且解除了它的背景   假设&#34;

让我们详细说明证明部分。

assoc' Zy my py t = t案例中n等于Zy,因此我们有

((Zy :+ m) :+ p) ~ (Zy :+ (m :+ p))

减少到

(m :+ p) ~ (m :+ p)

这显然是身份,因此我们可以解除这个假设并返回t

在每个递归步骤中,我们维护

((n :+ m) :+ p) ~ (n :+ (m :+ p))

方程。因此当assoc' (Sy ny) my py t = assoc ny my py t等式变为

((Sy n :+ m) :+ p) ~ (Sy n :+ (m :+ p))

减少到

 Sy ((n :+ m) :+ p) ~ Sy (n :+ (m :+ p))

由于(:+)的定义。因为构造函数是单射的

constructors_are_injective :: S n ~ S m => Vec a n -> Vec a m
constructors_are_injective xs = xs

等式变为

((n :+ m) :+ p) ~ (n :+ (m :+ p))

我们可以递归地调用assoc'

最后在coerce'的电话中,这两个词是统一的:

 1. ((n :+ m) :+ p) ~ (n :+ (m :+ p)) => Vec a (n :+ (m :+ p))
 2.                                      Vec a ((n :+ m) :+ p)

显然Vec a ((n :+ m) :+ p) Vec a (n :+ (m :+ p))假设为((n :+ m) :+ p) ~ (n :+ (m :+ p))

答案 1 :(得分:4)

  

我本来期望一个等式证明有一个更像这样的类型:

Natty x1 → ... → Natty xn → l :~: r

这是一个合理的选择。事实上,它在逻辑上等同于Hasochism论文中的那个:

{-# LANGUAGE GADTs, RankNTypes, TypeOperators, ScopedTypeVariables #-}
module Hasochism where

data l :~: r where
  Refl :: l :~: l

type Hasoc l r = forall t. (l ~ r => t) -> t

lemma1 :: forall l r. Hasoc l r -> l :~: r
lemma1 h = h Refl 

lemma2 :: forall l r. l :~: r -> Hasoc l r
lemma2 Refl t = t

从某种意义上说,Hasoc l r是约束l ~ r的不可预测的编码。

Hasochistic变体比:~:变体更容易使用,因为一旦你有了。

type family A a
-- ...
proof1 :: Proxy a -> Hasoc a (A a)
proof1 _ = -- ...

你可以像在

中一样使用它
use1 :: forall a. [a] -> [A a]
use1 t = proof1 (Proxy :: Proxy a) t

相反,使用

proof2 :: Proxy a -> a :~: A a
proof2 _ = -- ...

你需要

use2 :: forall a. [a] -> [A a]
use2 t = case proof2 (Proxy :: Proxy a) of Refl -> t

答案 2 :(得分:4)

我们有一些很好的答案,但作为肇事者,我想我会发表一些评论。

是的,这些引理有多个等效的表述。我使用的演示文稿就是其中之一,选择主要是务实的。这些天(在最近的代码库中),我甚至可以定义

-- Holds :: Constraint -> *
type Holds c = forall t . (c => t) -> t

这是一个消除器类型的示例:它抽象了它提供的内容(消除的动机)并且它要求你构造零个或多个方法(一个,这里)在更具体的情况下实现动机。阅读它的方法是向后。它说

  

如果您遇到问题(存在任何动机类型t),并且没有其他人可以提供帮助,也许您可​​以通过在方法中假设约束c来取得进展。

鉴于约束语言允许连接(也称为tupling),我们获得了编写形式的引理的方法

lemma :: forall x1 .. xn. (p1[x1 .. xn],.. pm[x1 .. xn])        -- premises
                       => t1[x1 .. xn] -> .. tl[x1 .. xn]       -- targets
                       -> Holds (c1[x1 .. xn],.. ck[x1 .. xn])  -- conclusions

甚至可能是某个约束,前提p或结论c具有等式的形式

l[x1 .. xn] ~ r[x1 .. cn]

现在,要部署这样的lemma,请考虑填补漏洞的问题

_ :: Problem

通过消除_优化此lemma,指定目标动机来自手头的问题。 方法(在Holds的情况下为单数)仍然保持打开状态。

lemma target1 .. targetl $ _

并且方法孔不会改变类型

_ :: Problem

但是GHC会知道更多的东西,因此更有可能相信你的解决方案。

有时候,有一个约束 - 数据选择来决定什么是(约束)前提和什么是(数据)目标。我倾向于选择这些以避免歧义(Simon喜欢猜测x1 .. xn,但有时需要提示)并通过归纳来促进证明,这对目标来说更容易(通常是单身人士)对于类型级数据而言,不是在内部。

关于部署,对于方程式,您当然可以切换到数据类型表示并打破案例分析

case dataLemma target1 .. targetl of Refl -> method

事实上,如果你装备了Dict存在主义

data Dict (c :: Constraint) :: * where
  Dict :: c => Dict c
你可以马上做一堆

case multiLemma blah blah blah of (Refl, Dict, Dict, Refl) -> method

但是当最多只有一个方法时,消除器形式更紧凑,更易读。实际上,我们可以链接多个引理而不会向右滑动

lemma1 ..   $
...
lemmaj ..   $
method

如果你有两个或两个以上的消除器,我认为将它作为GADT包装通常会更好,这样使用网站可以用构造函数标签来帮助标记每个案例。

无论如何,是的,重点是选择事实的呈现,这些事实最紧凑地使我们能够扩展GHC的约束解决机制的范围,以便更多的东西只是强调。如果你和西蒙在一起,那么向隔壁的迪米特里斯解释自己通常是一个很好的策略。