我想为Data.AVL.Indexed.Tree
定义一个仿函数实例。这似乎很棘手,因为存储树的上限和下限的索引的类型Key⁺
“依赖于”树中值的类型(或类型族)。
我想要做的是在下面实施fmap
:
open import Relation.Binary
open import Relation.Binary.PropositionalEquality
module Temp
{k ℓ}
{Key : Set k}
{_<_ : Rel Key ℓ}
(isStrictTotalOrder : IsStrictTotalOrder _≡_ _<_) where
open import Function
module AVL (Value : Set)
where open import Data.AVL (const Value) isStrictTotalOrder public
open AVL
fmap : {A B : Set} → (A → B) → ∀ {l u h} →
Indexed.Tree A l u h → Indexed.Tree B {!!} {!!} {!!}
fmap f = {!!}
现在我忽略了依赖类型的树,而是假设值具有常量类型。我们的想法是创建AVL
模块的本地变体,仅在类型Value
上进行参数化,并在不同类型实例化,以便给出fmap
的签名。
问题是,我似乎无法以这样的方式量化l
和u
,我可以通过相同的界限到Indexed.Tree
的两个不同实例。我想我明白为什么会这样:有两种不同的Key⁺
类型,一种用于Indexed.Tree A
,另一种用于Indexed.Tree B
。但我希望这些类型是相同的。
我是否遗漏了一些明显的内容,或Data.AVL
只是没有参数设置允许我定义fmap
?
答案 0 :(得分:2)
你在这里没有遗漏任何明显的东西。尽管Key⁺
忽略了Value
参数,但Agda仍然将它们视为不同的类型。但是,您可以非常轻松地编写转换函数。我将打开一些模块以保持名称可以忍受:
open Extended-key
open Height-invariants
open import Data.Nat
移动Key⁺
非常简单,因为基础Key
是相同的:
to : ∀ {A B} → Key⁺ A → Key⁺ B
to ⊥⁺ = ⊥⁺
to ⊤⁺ = ⊤⁺
to [ k ] = [ k ]
移动_<⁺_
关系也是可行的,您只需要在Key⁺
上进行模式匹配,以便将类型简化为(基本上)标识:
to< : ∀ {A B} l u → _<⁺_ A l u → _<⁺_ B (to l) (to u)
to< ⊥⁺ ⊥⁺ p = p
to< ⊥⁺ ⊤⁺ p = p
to< ⊥⁺ [ _ ] p = p
to< ⊤⁺ _ p = p
to< [ _ ] ⊥⁺ p = p
to< [ _ ] ⊤⁺ p = p
to< [ _ ] [ _ ] p = p
现在,看起来这应该可以解决这个问题,但是当你试图把它写下来时,你很快就会发现你需要一种方法来运输平衡。再一次,没什么特别的:
to∼ : ∀ {A B h₁ h₂} → _∼_ A h₁ h₂ → _∼_ B h₁ h₂
to∼ ∼+ = ∼+
to∼ ∼0 = ∼0
to∼ ∼- = ∼-
fmap
的类型如下所示:
fmap : ∀ {A B} (f : ∀ {x} → A x → B x) {l u h} →
Indexed.Tree A l u h → Indexed.Tree B (to l) (to u) h
这不是一个“真正的”fmap
,但考虑到你需要有不同的Key⁺
,这是我们能够得到的。
leaf
案例需要使用to<
,但其他方面却是微不足道的:
fmap f {lb} {ub} (Indexed.leaf l<u) = Indexed.leaf (to< lb ub l<u)
node
案例是事情变得有趣的地方。显而易见的解决方案:
fmap f (Indexed.node (k , v) l r bal) =
Indexed.node (k , f v) (fmap f l) (fmap f r) (to∼ bal)
没有进行类型检查。让我们找出原因。以下是上述表达式的类型和目标类型:
-- Have:
Indexed.Tree .B (to .l) (to .u) (suc (max .B (to∼ bal)))
-- Goal:
Indexed.Tree .B (to .l) (to .u) (suc (max .A bal))
所以我们需要一个额外的证明:
max≡ : ∀ {A B} {h₁ h₂} (bal : _∼_ A h₁ h₂) →
max A bal ≡ max B (to∼ bal)
max≡ ∼+ = refl
max≡ ∼0 = refl
max≡ ∼- = refl
最后,我们可以通过max≡ bal
重写目标类型并获得所需的实现:
fmap f (Indexed.node (k , v) l r bal) rewrite max≡ bal =
Indexed.node (k , f v) (fmap f l) (fmap f r) (to∼ bal)
我也在使用树的更依赖变体。这是通过将AVL
模块定义为:
import Data.AVL
module AVL (Value : Key → Set)
= Data.AVL Value isStrictTotalOrder
然后只需要映射函数来尊重Key
值:
mapping-function : {A B : Key → Set} {x : Key} → A x → B x
还有另一种选择:重写Data.AVL
模块,Extended-key
和Height-invariants
不会被Value
参数化。虽然这需要更改标准库,但我认为这是更好的解决方案。 Extended-key
和Heigh-invariants
在Data.AVL
之外肯定有用 - 事实上,我有一个Data.BTree
正好使用它。
如果您决定采用这种方式,请考虑将它们分成新模块(例如Data.ExtendedKey
)并提交补丁/拉取请求。