【问题标题】:Functor instance for Data.AVLData.AVL 的函子实例
【发布时间】:2014-01-26 21:48:58
【问题描述】:

我想为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 的签名。

问题是我似乎无法量化lu 的边界,以便我可以将 same 边界传递给两个不同的实例的Indexed.Tree。我想我明白这是为什么了:有两种不同的Key⁺ 类型,一种用于Indexed.Tree A,另一种用于Indexed.Tree B。但我希望这些类型相同。

我在这里遗漏了什么明显的东西,还是Data.AVL 只是没有以允许我定义fmap 的方式进行参数化?

【问题讨论】:

    标签: parameters functor agda


    【解决方案1】:

    您不会在这里遗漏任何明显的东西。即使Key⁺ 忽略了Value 参数,Agda 仍然将它们视为不同的类型。但是,您可以很容易地编写转换函数。我将打开一些模块以保持名称可以接受:

    open Extended-key
    open Height-invariants
    
    open import Data.Nat
    

    移动Key⁺ 非常容易,因为底层Keys 是相同的:

    to : ∀ {A B} → Key⁺ A → Key⁺ B
    to ⊥⁺    = ⊥⁺
    to ⊤⁺    = ⊤⁺
    to [ k ] = [ k ]
    

    移动_&lt;⁺_ 关系也是可行的,您只需要在Key⁺s 上进行模式匹配,以便将类型简化为(基本上)身份:

    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⁺s,这是我们可以得到的最接近的。

    leaf case 需要使用to&lt;,但在其他方面是微不足道的:

    fmap f {lb} {ub} (Indexed.leaf l<u) = Indexed.leaf (to< lb ub l<u)
    

    node case 是事情变得有趣的地方。显而易见的解决方案:

    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-keyHeight-invariants 不被Value 参数化。虽然这需要更改标准库,但我认为这是更好的解决方案。 Extended-keyHeigh-invariantsData.AVL 之外肯定有用 - 事实上,我有一个 Data.BTree 正是使用它。

    如果您决定采用这种方式,请考虑将它们分成新模块(例如Data.ExtendedKey)并提交补丁/拉取请求。

    【讨论】:

    • 啊,有道理。我什至没有想过以这种方式映射类型及其操作。但是,是的,一个人最终得到的不是真正的fmap 这一事实并不理想。我倾向于将Extended-keyHeight-invariants 吸出到一个单独的模块中。 (我还在为我最近提到的FiniteMap 数据类型重用Extended-key,这样确实会更方便。)也许这应该是一个单独的问题,但我也想知道@987654363 的排序约束是否@ 可以同时证明无关?
    • 我非常快速地查看了证明无关性(对于绑定在叶子上的 lheadTail 和 initLast 进行细微更改. (但我还没有正确尝试过。)无论如何,再次感谢您的出色回答。
    • @Roly:关于Extended-key,其实我前段时间做了一个这样的模块。认为您可能会发现它很有用:gist.github.com/vituscze/8673269
    • 谢谢。我还实现了类似于您的 IsStrictTotalOrder 实例的东西,尽管我没有费心对任意相等进行参数化,因为我在自己的开发中已经摆脱了这种风格。但是,是的,显示严格的总订单在函子 (+ 2) 下关闭似乎比简单地显示保留传递性更好,如Data.AVL.Extended-key
    猜你喜欢
    • 2023-03-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-05
    • 1970-01-01
    • 2021-02-23
    • 1970-01-01
    • 2013-06-10
    相关资源
    最近更新 更多