什么是直觉型理论的组合逻辑等价物?

87

我最近完成了一门大学课程,其中使用了Haskell和Agda(一种依赖类型的函数式编程语言),我想知道是否能用组合逻辑替换这些语言中的λ演算。对于Haskell而言,使用S和K组合子似乎是可行的,从而使其成为无点风格。我想知道在Agda中的等效方法是什么。也就是说,是否可以构建一个不使用任何变量的依赖型函数式编程语言,相当于Agda?

此外,是否可能通过组合子来替换量词?我不知道这是否巧合,例如,普遍量化使类型签名看起来像λ表达式。有没有一种方法可以从类型签名中删除普遍量化而不改变其含义?例如:

forall a : Int -> a < 0 -> a + a < a

是否可以不使用forall来表达同样的意思?


21
从确定K的最依赖类型开始(比较简单),接着再确定S的最依赖类型(相对复杂)。如果加入Set和Pi的常量,然后尝试重新构建基本的(不一致的)Set:Set系统将会很有趣。我还可以继续思考,但我得去赶飞机了。 - pigworker
2个回答

54
所以我想了一下并取得了一些进展。这是对Martin-Löf令人愉悦的简单(但不一致)Set:Set系统在组合样式中进行编码的第一次尝试。这不是一个好的完成方式,但它是最容易入手的地方。这种类型理论的语法只是带有类型注释、Pi类型和一个宇宙集合的λ演算。

目标类型理论

为了完整起见,我将介绍规则。上下文有效性只是说您可以通过添加居住于Set的新变量来从空白构建上下文。
                     G |- valid   G |- S : Set
--------------     ----------------------------- x fresh for G
  . |- valid         G, x:S |- valid

现在我们可以说如何为任何给定的上下文中的术语合成类型,以及如何根据它包含的术语的计算行为更改某个东西的类型。

  G |- valid             G |- S : Set   G |- T : Pi S \ x:S -> Set
------------------     ---------------------------------------------
  G |- Set : Set         G |- Pi S T : Set

  G |- S : Set   G, x:S |- t : T x         G |- f : Pi S T   G |- s : S
------------------------------------     --------------------------------
  G |- \ x:S -> t : Pi S T                 G |- f s : T s

  G |- valid                  G |- s : S   G |- T : Set
-------------- x:S in G     ----------------------------- S ={beta} T
  G |- x : S                  G |- s : T

在与原文稍有不同的变化中,我将lambda作为唯一的绑定运算符,因此Pi的第二个参数应该是一个计算返回类型如何取决于输入的函数。按照惯例(例如在Agda中,但可悲的是不适用于Haskell),lambda的范围向右延伸尽可能远,因此当它们是高阶运算符的最后一个参数时,你通常可以省略抽象括号:你可以看到我在Pi中这样做了。您的Agda类型(x:S) -> T变成了Pi S \ x:S -> T
(插曲。如果您想能够合成抽象的类型,则需要对lambda进行类型注释。如果您将类型检查作为自己的操作方式,则仍然需要注释来检查像(\ x -> t)s这样的beta-redex,因为您无法从整体的类型中猜测部分的类型。我建议现代设计师检查类型并从语法中排除beta-redexes。)
(插曲。这个系统是不一致的,因为Set:Set允许编码各种“说谎者悖论”。当Martin-Löf提出这个理论时,Girard向他发送了自己不一致的System U中的编码。Hurkens由于随后的悖论是我们所知道的最毒瘤的构造。)
组合子语法和规范化
无论如何,我们有两个额外的符号Pi和Set,因此我们也许可以用S、K和两个额外的符号(我选择了U表示宇宙和P表示乘积)进行组合转换。
现在我们可以定义未打类型标记的组合语法(带自由变量)。
data SKUP = S | K | U | P deriving (Show, Eq)

data Unty a
  = C SKUP
  | Unty a :. Unty a
  | V a
  deriving (Functor, Eq)
infixl 4 :.

请注意,我已经在这个语法中包含了表示类型a的自由变量的手段。除了是我的一个条件反射(每一个值得称赞的语法都是一个带有return嵌入变量和>>=执行替换的自由monad),它还将方便地表示转换绑定术语到其组合形式过程中的中间阶段。
以下是规范化:
norm :: Unty a -> Unty a
norm (f :. a)  = norm f $. a
norm c         = c

($.) :: Unty a -> Unty a -> Unty a        -- requires first arg in normal form
C S :. f :. a $. g  = f $. g $. (a :. g)  -- S f a g = f g (a g)   share environment
C K :. a $. g       = a                   -- K a g = a             drop environment
n $. g              = n :. norm g         -- guarantees output in normal form
infixl 4 $.

一个读者的练习是定义一种类型,用于精确表示正规形式,并锐化这些操作的类型。

表达类型理论

现在我们可以为我们的类型理论定义语法。

data Tm a
  = Var a
  | Lam (Tm a) (Tm (Su a))    -- Lam is the only place where binding happens
  | Tm a :$ Tm a
  | Pi (Tm a) (Tm a)          -- the second arg of Pi is a function computing a Set
  | Set
  deriving (Show, Functor)
infixl 4 :$

data Ze
magic :: Ze -> a
magic x = x `seq` error "Tragic!"

data Su a = Ze | Su a deriving (Show, Functor, Eq)

我在Bellegarde和Hook的方式(由Bird和Paterson普及)中使用de Bruijn索引表示法。类型Su aa多一个元素,我们将其用作绑定器下自由变量的类型,其中Ze是新绑定的变量,Su x是旧自由变量x的移位表示。

将术语翻译为组合子

完成这一步后,我们获得了基于“括号抽象”的常规翻译。
tm :: Tm a -> Unty a
tm (Var a)    = V a
tm (Lam _ b)  = bra (tm b)
tm (f :$ a)   = tm f :. tm a
tm (Pi a b)   = C P :. tm a :. tm b
tm Set        = C U

bra :: Unty (Su a) -> Unty a               -- binds a variable, building a function
bra (V Ze)      = C S :. C K :. C K        -- the variable itself yields the identity
bra (V (Su x))  = C K :. V x               -- free variables become constants
bra (C c)       = C K :. C c               -- combinators become constant
bra (f :. a)    = C S :. bra f :. bra a    -- S is exactly lifted application

输入组合器

翻译展示了我们使用组合器的方式,这让我们对它们的类型有了相当的线索。 UP 只是集合构造函数,因此,写出未翻译的类型,并允许使用“Agda符号”表示 Pi,我们应该有:

U : Set
P : (A : Set) -> (B : (a : A) -> Set) -> Set
< p > K 组合子用于将某种类型 A 的值提升为另一种类型 G 上的常数函数。

  G : Set   A : Set
-------------------------------
  K : (a : A) -> (g : G) -> A
< p > S组合子用于将应用程序提升到类型之上,所有部分都可以依赖于该类型。

  G : Set
  A : (g : G) -> Set
  B : (g : G) -> (a : A g) -> Set
----------------------------------------------------
  S : (f : (g : G) ->    (a : A g) -> B g a   ) ->
      (a : (g : G) ->    A g                  ) ->
           (g : G) ->    B g (a g)

如果您查看S的类型,您会发现它确切地说明了类型理论的上下文应用规则,这就使其适合反映应用结构。这就是它的工作!然后我们只对封闭事物进行应用。
  f : Pi A B
  a : A
--------------
  f a : B a

但是有一个问题。我用普通类型理论写了组合子的类型,而不是组合类型理论。幸运的是,我有一台机器可以进行翻译。

组合类型系统

---------
  U : U

---------------------------------------------------------
  P : PU(S(S(KP)(S(S(KP)(SKK))(S(KK)(KU))))(S(KK)(KU)))

  G : U
  A : U
-----------------------------------------
  K : P[A](S(S(KP)(K[G]))(S(KK)(K[A])))

  G : U
  A : P[G](KU)
  B : P[G](S(S(KP)(S(K[A])(SKK)))(S(KK)(KU)))
--------------------------------------------------------------------------------------
  S : P(P[G](S(S(KP)(S(K[A])(SKK)))(S(S(KS)(S(S(KS)(S(KK)(K[B])))(S(KK)(SKK))))
      (S(S(KS)(KK))(KK)))))(S(S(KP)(S(S(KP)(K[G]))(S(S(KS)(S(KK)(K[A])))
      (S(S(KS)(KK))(KK)))))(S(S(KS)(S(S(KS)(S(KK)(KP)))(S(KK)(K[G]))))
      (S(S(KS)(S(S(KS)(S(KK)(KS)))(S(S(KS)(S(S(KS)(S(KK)(KS)))
      (S(S(KS)(S(KK)(KK)))(S(KK)(K[B])))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(KK)(KK))))
      (S(KK)(KK))))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(S(KS)(S(KK)(KK)))
      (S(S(KS)(KK))(KK)))))(S(S(KS)(S(S(KS)(S(KK)(KS)))(S(KK)(KK))))(S(KK)(KK)))))))

  M : A   B : U
----------------- A ={norm} B
  M : B

这就是所有的内容,含义难以理解:一个 Set:Set 的组合表示!
还存在一个问题。系统的语法不提供任何方法来从术语中猜测 SGAB 参数,同样对于 K 也是如此。相应地,我们可以通过算法验证类型推导,但不能像原始系统那样对组合器术语进行类型检查。可能有效的方法是要求输入类型检查器在使用 S 和 K 时带有类型注释,有效记录推导过程。但这又是另一个棘手的问题……
如果你已经足够热心地开始了,这就是一个好的停止点了。其余的都是“幕后”内容。
生成组合器的类型
我使用相关类型理论术语的括号抽象转换生成了这些组合类型。为了展示我是如何做到的,并使本文不完全无意义,让我提供我的设备。
我可以将组合器的类型完全抽象化,包括它们的参数,如下所示。我利用了我的便捷函数 pil,它结合了 Pi 和 lambda,避免了重复域类型,并且相当方便地允许我使用 Haskell 的函数空间绑定变量。也许你几乎可以读懂以下内容!
pTy :: Tm a
pTy = fmap magic $
  pil Set $ \ _A -> pil (pil _A $ \ _ -> Set) $ \ _B -> Set

kTy :: Tm a
kTy = fmap magic $
  pil Set $ \ _G -> pil Set $ \ _A -> pil _A $ \ a -> pil _G $ \ g -> _A

sTy :: Tm a
sTy = fmap magic $
  pil Set $ \ _G ->
  pil (pil _G $ \ g -> Set) $ \ _A ->
  pil (pil _G $ \ g -> pil (_A :$ g) $ \ _ -> Set) $ \ _B ->
  pil (pil _G $ \ g -> pil (_A :$ g) $ \ a -> _B :$ g :$ a) $ \ f ->
  pil (pil _G $ \ g -> _A :$ g) $ \ a ->
  pil _G $ \ g -> _B :$ g :$ (a :$ g)

有了这些定义,我提取了相关的开放子项并通过翻译进行了处理。

de Bruijn编码工具包

以下是构建pil的方法。首先,我定义了一类用于变量的Finite集合。每个这样的集合都有一个将其嵌入到上面集合的构造函数保存的embbedding,以及一个新的top元素,并且您可以通过embd功能区分它们:如果某个值在emb的图像中,则embd函数会告诉您。

class Fin x where
  top :: Su x
  emb :: x -> Su x
  embd :: Su x -> Maybe x

当然,我们可以为ZeSuc实例化Fin

instance Fin Ze where
  top = Ze              -- Ze is the only, so the highest
  emb = magic
  embd _ = Nothing      -- there was nothing to embed

instance Fin x => Fin (Su x) where
  top = Su top          -- the highest is one higher
  emb Ze     = Ze            -- emb preserves Ze
  emb (Su x) = Su (emb x)    -- and Su
  embd Ze      = Just Ze           -- Ze is definitely embedded
  embd (Su x)  = fmap Su (embd x)  -- otherwise, wait and see

现在我可以使用“weakening”操作来定义小于等于关系。
class (Fin x, Fin y) => Le x y where
  wk :: x -> y
wk 函数应将 x 中的元素嵌入到 y 中作为最大的元素,以便 y 中的额外内容更小,因此在 de Bruijn 索引术语中,绑定更加局部。
instance Fin y => Le Ze y where
  wk = magic    -- nothing to embed

instance Le x y => Le (Su x) (Su y) where
  wk x = case embd x of
    Nothing  -> top          -- top maps to top
    Just y   -> emb (wk y)   -- embedded gets weakened and embedded

一旦你解决了这个问题,一些排名欺诈就能完成其余部分。
lam :: forall x. Tm x -> ((forall y. Le (Su x) y => Tm y) -> Tm (Su x)) -> Tm x
lam s f = Lam s (f (Var (wk (Ze :: Su x))))
pil :: forall x. Tm x -> ((forall y . Le (Su x) y => Tm y) -> Tm (Su x)) -> Tm x
pil s f = Pi s (lam s f)

高阶函数不仅给你一个表示变量的术语,它还会给你一个重载的东西,在任何可见变量的范围内成为正确的变量表示。也就是说,通过类型区分不同作用域的事实,足以让Haskell类型检查器计算所需的转换到de Bruijn表示的位移。为什么要自己养狗还要亲自去叫呢?


这可能很傻,但如果添加F组合器,这张图片会如何变化? F的行为取决于其第一个参数:如果A是原子,MN是术语,PQ是复合,则FAMN-> MF(PQ)MN-> NPQ。 这不能在SK(I)演算中表示,但可以将K表示为FF。 是否可以使用此扩展您的无点MLTT? - kram1032
我非常确定这个括号抽象过程有问题,特别是“组合子变为常数”的部分,将对于组合子 c ∈ {S,K,U,P} 的 λx.c 翻译为 Kc。问题在于,这些组合子是多态的,并且可能在依赖于 x 的类型上使用; 这种类型无法通过此翻译来保留。以具体例子来说,类型为 (A:Set)→(a:A)→A 的术语 λ(A:Set)→λ(a:A)→a 被翻译为 S(S(KS)(KK))(KK),它不能在第二个参数的类型取决于第一个参数的类型时使用。 - Anders Kaseorg

8
我猜“括号抽象”在某些情况下也适用于依赖类型。在以下论文的第5节中,您可以找到一些K和S类型:
不可思议但有意义的巧合 依赖型安全语法和求值 康纳·麦克布赖德(Conor McBride),斯特拉斯克莱德大学,2010年
将lambda表达式转换为组合表达式大致相当于将自然演绎证明转换为Hilbert样式证明。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接