通过trie终止检查功能

Rol*_*oly 3 recursion termination trie agda

我很难说服Agda终止检查fmap下面的函数,并且在a的结构上递归地定义了类似的函数Trie.A Trie是一个trie,其域是a Type,是一个由单元,产品和固定点组成的对象级类型(我省略了副产品以保持代码最小).问题似乎与我在定义中使用的类型级替换有关Trie.(表达式const (?? ?) * ?表示将替换const (?? ?)应用于类型?.)

module Temp where

   open import Data.Unit
   open import Category.Functor
   open import Function
   open import Level
   open import Relation.Binary

   -- A context is just a snoc-list.
   data Cxt {} (A : Set ) : Set  where
      ? : Cxt A
      _??_ : Cxt A ? A ? Cxt A

   -- Context membership.
   data _?_ {} {A : Set } (a : A) : Cxt A ? Set  where
      here : ? {?} ? a ? ? ?? a
      there : ? {? a?} ? a ? ? ? a ? ? ?? a?
   infix 3 _?_

   -- Well-formed types, using de Bruijn indices.
   data _? (? : Cxt ?) : Set where
      nat : ? ?
       : ? ?
      var : _ ? ? ? ? ?
      _+_ _?_ : ? ? ? ? ? ? ? ?
      ? : ? ?? _ ? ? ? ?
   infix 3 _?

   -- A closed type.
   Type : Set
   Type = ? ?

   -- Type-level substitutions and renamings.
   Sub Ren : Rel (Cxt ?) zero
   Sub ? ?? = _ ? ? ? ?? ?
   Ren ? ?? = ? {x} ? x ? ? ? x ? ??

   -- Renaming extension.
   extend? : ? {? ??} ? Ren ? ?? ? Ren (? ?? _) (?? ?? _)
   extend? ? here = here
   extend? ? (there x) = there (? x)

   -- Lift a type renaming to a type.
   _*?_ : ? {? ??} ? Ren ? ?? ? ? ? ? ?? ?
   _ *? nat = nat
   _ *?  = 
   ? *? (var x) = var (? x)
   ? *? (?? + ??) = (? *? ??) + (? *? ??)
   ? *? (?? ? ??) = (? *? ??) ? (? *? ??)
   ? *? (? ?) = ? (extend? ? *? ?)

   -- Substitution extension.
   extend : ? {? ??} ? Sub ? ?? ? Sub (? ?? _) (?? ?? _)
   extend ? here = var here
   extend ? (there x) = there *? (? x)

   -- Lift a type substitution to a type.
   _*_ : ? {? ??} ? Sub ? ?? ? ? ? ? ?? ?
   ? * nat = nat
   ? *  = 
   ? * var x = ? x
   ? * (?? + ??) = (? * ??) + (? * ??)
   ? * (?? ? ??) = (? * ??) ? (? * ??)
   ? * ? ? = ? (extend ? * ?)

   data Trie {} (A : Set ) : Type ? Set  where
      ?? : A ?  ? A
      ?_,_? : ? {?? ??} ? ?? ? A ? ?? ? A ? ?? + ?? ? A
      ?_ : ? {?? ??} ? ?? ? ?? ? A ? ?? ? ?? ? A
      roll : ? {?} ? (const (? ?) * ?) ? A ? ? ? ? A

   infixr 5 Trie
   syntax Trie A ? = ? ? A

   {-# NO_TERMINATION_CHECK #-}
   fmap : ? {a} {A B : Set a} {?} ? (A ? B) ? ? ? A ? ? ? B
   fmap f (?? x)    = ?? (f x)
   fmap f ? ?? , ?? ? = ? fmap f ?? , fmap f ?? ?
   fmap f (? ?)    = ? (fmap (fmap f) ?)
   fmap f (roll ?) = roll (fmap f ?)
Run Code Online (Sandbox Code Playgroud)

fmap在每种情况下,似乎都会在一个极小的论点中进行处理; 当然,如果我删除递归类型,产品案例就没问题.另一方面,如果我删除产品,定义会处理递归类型.

在这里最简单的方法是什么?该内嵌/保险丝招看起来并不特别适用,但也许是.或者我应该寻找另一种方法来处理定义中的替换Trie

Vit*_*tus 5

内联/熔丝技巧可以(可能)以令人惊讶的方式应用.这个技巧适用于这类问题:

data Trie (A : Set) : Set where
  nil  :                     Trie A
  node : A ? List (Trie A) ? Trie A

map-trie : {A B : Set} ? (A ? B) ? Trie A ? Trie B
map-trie f nil = nil
map-trie f (node x xs) = node (f x) (map (map-trie f) xs)
Run Code Online (Sandbox Code Playgroud)

此函数在结构上是递归的,但是以隐藏的方式.map只适用map-trie f于元素xs,因此map-trie应用于较小(子)尝试.但是Agda没有通过定义map来看它没有做任何时髦的事情.所以我们必须应用内联/保险丝技巧让它通过终止检查器:

map-trie : {A B : Set} ? (A ? B) ? Trie A ? Trie B
map-trie         f nil = nil
map-trie {A} {B} f (node x xs) = node (f x) (map? xs)
  where
  map? : List (Trie A) ? List (Trie B)
  map? [] = []
  map? (x ? xs) = map-trie f x ? map? xs
Run Code Online (Sandbox Code Playgroud)

您的fmap函数共享相同的结构,您可以映射某种提升的函数.但是要内联什么?如果我们按照上面的例子,我们应该内联fmap.这看起来有点奇怪,但确实有效:

fmap fmap? : ? {a} {A B : Set a} {?} ? (A ? B) ? ? ? A ? ? ? B

fmap  f (?? x) = ?? (f x)
fmap  f ? ?? , ?? ? = ? fmap f ?? , fmap f ?? ?
fmap  f (? ?) = ? (fmap (fmap? f) ?)
fmap  f (roll ?) = roll (fmap f ?)

fmap? f (?? x) = ?? (f x)
fmap? f ? ?? , ?? ? = ? fmap? f ?? , fmap? f ?? ?
fmap? f (? ?) = ? (fmap? (fmap f) ?)
fmap? f (roll ?) = roll (fmap? f ?)
Run Code Online (Sandbox Code Playgroud)

您可以应用另一种技术:它称为大小类型.您可以直接指定它,而不是依赖于编译器来确定somethig何时是结构递归.但是,您必须按类型索引数据类型Size,因此这种方法相当具有侵入性,无法应用于现有类型,但我认为值得一提.

在其最简单的形式中,大小类型表现为由自然数索引的类型.该索引指定结构大小的上限.您可以将其视为树高度的上限(假设数据类型是某个仿函数F的F分支树).大小版本的List外观几乎像一个Vec,例如:

data SizedList (A : Set) : ? ? Set where
  []  : ? {n} ? SizedList A n
  _?_ : ? {n} ? A ? SizedList A n ? SizedList A (suc n)
Run Code Online (Sandbox Code Playgroud)

但是大小的类型添加了一些使它们更易于使用的功能.?当你不关心大小时,你有一个常数.suc被称为?和Agda实现的规则很少,例如? ? = ?.

让我们重写一下Trie使用大小类型的例子.我们需要在文件顶部的一个pragma和一个导入:

{-# OPTIONS --sized-types #-}
open import Size
Run Code Online (Sandbox Code Playgroud)

这是修改后的数据类型:

data Trie (A : Set) : {i : Size} ? Set where
  nil  : ? {i}                         ? Trie A {? i}
  node : ? {i} ? A ? List (Trie A {i}) ? Trie A {? i}
Run Code Online (Sandbox Code Playgroud)

如果您按map-trie原样保留该功能,终止检查程序仍会抱怨.那是因为当你没有指定任何大小时,Agda将填入无限(即无关值),我们回到了开头.

但是,我们可以标记map-trie为保持大小:

map-trie : ? {i A B} ? (A ? B) ? Trie A {i} ? Trie B {i}
map-trie f nil         = nil
map-trie f (node x xs) = node (f x) (map (map-trie f) xs)
Run Code Online (Sandbox Code Playgroud)

所以,如果你给它一个Trie限制i,它也会给你另一个Trie限制i.所以map-trie永远不能做出Trie更大,只有同样大或更小.这足以让终止检查器弄清楚这map (map-trie f) xs没关系.


此技术也可以应用于您的Trie:

open import Size
  renaming (?_ to ^_)

data Trie {} (A : Set ) : {i : Size} ? Type ? Set  where
  ??    : ? {i} ? A ?
    Trie A {^ i} 
  ?_,_? : ? {i ?? ??} ? Trie A {i} ?? ? Trie A {i} ?? ?
    Trie A {^ i} (?? + ??)
  ?_    : ? {i ?? ??} ? Trie (Trie A {i} ??) {i} ?? ?
    Trie A {^ i} (?? ? ??)
  roll  : ? {i ?} ? Trie A {i} (const (? ?) * ?) ?
    Trie A {^ i} (? ?)

infixr 5 Trie
syntax Trie A ? = ? ? A

fmap : ? {i } {A B : Set } {?} ? (A ? B) ? Trie A {i} ? ? Trie B {i} ?
fmap f (?? x) = ?? (f x)
fmap f ? ?? , ?? ? = ? fmap f ?? , fmap f ?? ?
fmap f (? ?) = ? fmap (fmap f) ?
fmap f (roll ?) = roll (fmap f ?)
Run Code Online (Sandbox Code Playgroud)