I would like to define a functor instance for Data.AVL.Indexed.Tree
. This seems to be tricky because of the way the type Key⁺
of the indices storing the upper and lower bounds of the tree "depend on" the type (or family of types) of the values in the tree.
What I want to do is implement fmap
below:
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 = {!!}
For now I'm ignoring dependently-typed trees, and instead assuming the values have a constant type. The idea is to create a local variant of the AVL
module, parameterised only on a type Value
, and instantiate that at different types in order to give the signature of fmap
.
The problem is that I then don't seem to be able to quantify over the bounds l
and u
in such a way that I can pass the same bounds to two different instances of Indexed.Tree
. I think I see why this is: there are two different Key⁺
types, one for Indexed.Tree A
and one for Indexed.Tree B
. But I want these types to be the same.
Am I missing something obvious here, or is Data.AVL
just not parameterised in way that allows me to define fmap
?
You're not missing anything obvious here. Even though
Key⁺
ignores theValue
parameter, Agda still treats them as a different types. However, you can write the conversion functions quite easily. I'll open some modules to keep the names bearable:Moving the
Key⁺
is very easy, since the underlyingKey
s are the same:Moving the
_<⁺_
relation is also doable, you just need to pattern match on bothKey⁺
s so that the type is reduced to (basically) identity:Now, it might seem that this should do the trick, but when you try to write it down, you'll soon find out you need a way to transport the balance as well. Again, nothing extraordinary:
The type of
fmap
then looks like this:It's not a "true"
fmap
, but considering you need to have differentKey⁺
s, this is as close as we can get.leaf
case requires usage ofto<
, but is otherwise trivial:node
case is where things get interesting. The obvious solution:does not typecheck. Let's figure out why. Here are the types of the expression above and the goal type:
So we need one extra proof:
And finally, we can rewrite the goal type via
max≡ bal
and get the desired implementation:I'm also using the more dependent variant of the tree. This is done by defining
AVL
module as:And then simply requiring the mapping function to respect the
Key
value:There's another option: rewrite the
Data.AVL
module such thatExtended-key
andHeight-invariants
are not parametrised byValue
. While this requires a change in the standard library, I think it's the better solution.Extended-key
andHeigh-invariants
could certainly be useful outside ofData.AVL
- in fact, I have aData.BTree
that uses exactly that.If you decide to go this way, consider separating them into new modules (such as
Data.ExtendedKey
) and submitting a patch/pull request.