也许这是一个愚蠢的问题。这是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,返回l
和r
相等的命题的证明" (使用GHC' Data.Type.Equality)。这对我来说更有意义,并且似乎与你在其他依赖类型的系统中所说的一致。我猜这相当于论文中的版本,但我很难在心理上将这两个版本放在一边。
简而言之,我很困惑。我觉得我错过了一个关键的洞察力。我应该如何阅读((l ~ r) => t) -> t
类型?
答案 0 :(得分:6)
我将其视为"给定一个需要
l ~ r
的术语,请将其返回 术语#34;
它给出了一个类型包含l
的术语,返回该术语,其中所有l
被r
s替换为类型" (或在另一个方向r -> l
)。这是一个非常巧妙的技巧,允许您将所有cong
,trans
,subst
和类似内容委托给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的约束解决机制的范围,以便更多的东西只是强调。如果你和西蒙在一起,那么向隔壁的迪米特里斯解释自己通常是一个很好的策略。