Zulip Chat Archive

Stream: new members

Topic: What is that `.` in `rbmap`'s definition?


Marko Grdinić (Oct 19 2019 at 08:02):

def rbmap (α : Type u) (β : Type v) (lt : α  α  Prop . rbtree.default_lt) : Type (max u v) :=
rbtree (α × β) (rbmap_lt lt)

I am wondering what Prop . rbtree.default_lt is supposed to be. The . operator here does not seem to be a function so it is probably some kind of built in, but I am not sure what it does.

It is too bad that β here is not dependent on α. I have a use case where that would be useful and am going to try to redesign the rbmap. It is good there is not much there.

Kenny Lau (Oct 19 2019 at 08:08):

it's \a \to \a \to Prop defaulting to rbtree.default_lt

Marko Grdinić (Oct 19 2019 at 08:19):

@Kenny Lau

I see. Does it get printed like lt : auto_param (α → α → Prop) (name.mk_string "default_lt" (name.mk_string "rbtree" name.anonymous)) for you?

Also...

def rbmap_lt {α : Type u} {β : Type u  Type v} (lt : α  α  Prop) (a b : Σ α, β α) : Prop :=
lt a.1 b.1

set_option auto_param.check_exists false

def rbmap (α : Type u) (β : Type u  Type v) (lt : α  α  Prop . rbtree.default_lt) : Type (max u v) :=
rbtree (Σ α, β α) (rbmap_lt lt)
type mismatch at application
  lt (a.fst)
term
  a.fst
has type
  Type u : Type (u+1)
but is expected to have type
  α : Type u

I am getting some type errors in both functions when trying to make a dependent map that I am not sure how to deal with. What is wrong here?

Marko Grdinić (Oct 19 2019 at 08:24):

def rbmap_lt {α : Type} {β : α  Type} (lt : α  α  Prop) (a b : Σ α, β α) : Prop :=
lt a.1 b.1

set_option auto_param.check_exists false

def rbmap (α : Type u) (β : α  Type v) (lt : α  α  Prop . rbtree.default_lt) : Type (max u v) :=
rbtree (Σ α, β α) (rbmap_lt lt)
type mismatch at application
  rbmap_lt lt
term
  lt
has type
  auto_param (α → α → Prop) (name.mk_string "default_lt" (name.mk_string "rbtree" name.anonymous)) : Type u
but is expected to have type
  ?m_1 → ?m_1 → Prop : Type

I've managed to figure out the first part. Now what is this?

Mario Carneiro (Oct 19 2019 at 08:26):

Notice that the type is Type instead of Type u

Mario Carneiro (Oct 19 2019 at 08:27):

the auto_param thing is not the problem

Mario Carneiro (Oct 19 2019 at 08:28):

When you defined rbmap_lt, you used Type for the type of alpha and beta (i.e. non universe polymorphic), but then you apply it in rbmap where alpha and beta now live in arbitrary universes

Marko Grdinić (Oct 19 2019 at 08:33):

def rbmap_lt {α : Type u} {β : α  Type v} (lt : α  α  Prop) (a b : Σ α, β α) : Prop :=
lt a.1 b.1

set_option auto_param.check_exists false

def rbmap (α : Type u) (β : α  Type v) (lt : α  α  Prop . rbtree.default_lt) : Type (max u v) :=
rbtree (Σ α, β α) (rbmap_lt lt)

So like this then. If I omit the universe levels in the type will it get inferred like so or will it be non-universe polymorphic? I can't check this easily because #check seems to fail with an error when I pass it this function.

Marko Grdinić (Oct 19 2019 at 08:34):

The error is invalid auto_param, unknown tactic 'rbtree.default_lt'.

Scott Morrison (Oct 19 2019 at 08:36):

Yeah, I was running into the same problem: rbtree.default_lt doesn't seem to actually exist...

Mario Carneiro (Oct 19 2019 at 08:36):

it's in init.data.rbtree.default

Mario Carneiro (Oct 19 2019 at 08:37):

this checks for me:

prelude
import init.data.rbtree.basic
universes u v

def rbmap_lt {α : Type u} {β : α  Type v} (lt : α  α  Prop) (a b : Σ α, β α) : Prop :=
lt a.1 b.1

set_option auto_param.check_exists false

def rbmap (α : Type u) (β : α  Type v) (lt : α  α  Prop . rbtree.default_lt) : Type (max u v) :=
rbtree (Σ α, β α) (rbmap_lt lt)

Mario Carneiro (Oct 19 2019 at 08:38):

If I don't use prelude then I get a duplicate definition error because this is all defined in init

Mario Carneiro (Oct 19 2019 at 08:41):

So like this then. If I omit the universe levels in the type will it get inferred like so or will it be non-universe polymorphic?

If you want to omit the universe level but keep it polymorphic, use Type* (or Type _ which is equivalent). The core library deliberately uses universe variables everywhere, but in mathlib the preference is for Type* unless universe inference fails

Marko Grdinić (Oct 19 2019 at 08:42):

If I don't use prelude then I get a duplicate definition error because this is all defined in init

Actually, I am using that as well as I just copy pasted the rbmap.basic into my own project. But even if I insert that #check rbmap in the original file, I get the same error.

Mario Carneiro (Oct 19 2019 at 08:43):

right, #check rbmap fails if you put it right after my code snippet

Mario Carneiro (Oct 19 2019 at 08:44):

If you import init.data.rbtree instead, then it calls the tactic (which fails)

Mario Carneiro (Oct 19 2019 at 08:45):

This is only a problem if you use the auto_param feature: that is, you leave implicit the lt argument

Mario Carneiro (Oct 19 2019 at 08:45):

#check @rbmap works fine

Mario Carneiro (Oct 19 2019 at 08:46):

obviously it can't call the tactic if it doesn't exist. Normally it errors when the auto_param is defined, but as you can see from the code snippet this check was explicitly disabled

Mario Carneiro (Oct 19 2019 at 08:47):

I'm not exactly sure why the definition was delayed, but I would guess it's because of bootstrapping the tactic framework

Marko Grdinić (Oct 19 2019 at 08:51):

Ok, I see. Just a bit more...

def drbmap_lt {α : Type u} {β : α  Type v} (lt : α  α  Prop) (a b : Σ α, β α) : Prop :=
lt a.1 b.1

I am a bit confused here. Why is this type definition right?

def drbmap_lt {α : Type u} {β : α  Type v} (lt : α  α  Prop) (a b : Σ (x : α), β x) : Prop :=
lt a.1 b.1

Shouldn't I have written it like this?

Marko Grdinić (Oct 19 2019 at 08:53):

drbmap :
  Π (α : Type u_1),
    (α → Type u_2) →
    auto_param (α → α → Prop) (name.mk_string "default_lt" (name.mk_string "rbtree" name.anonymous)) →
    Type (max u_1 u_2)

Also were you saying that the printing of the auto_param is not an error?

Mario Carneiro (Oct 19 2019 at 08:58):

The only difference between your two definitions is the name of the bound variable

Mario Carneiro (Oct 19 2019 at 08:58):

the first one is confusing because the bound variable α shadows the α earlier in the definition

Mario Carneiro (Oct 19 2019 at 09:00):

The auto_param thing is supposed to be there. A (x : foo . tac) binder is elaborated to (x : auto_param foo `tac). If you look at the definition, you will see that auto_param α tac := α, so this doesn't affect type correctness of anything. It just acts as a marker for the elaborator so it can remember what tactic you wanted to call in case the value is not provided

Mario Carneiro (Oct 19 2019 at 09:01):

A similar thing happens with opt_param, if you write (x : foo := val)

Marko Grdinić (Oct 19 2019 at 09:12):

def mem : α  rbnode α  Prop
| a leaf               := false
| a (red_node l v r)   := mem a l  (¬ lt a v  ¬ lt v a)  mem a r
| a (black_node l v r) := mem a l  (¬ lt a v  ¬ lt v a)  mem a r

Would it be worth it to change the above implementation of mem to the following one?

def mem : α  rbnode α  Prop
| a leaf               := false
| a (red_node l v r)   := (¬ lt a v  ¬ lt v a)  mem a l  mem a r
| a (black_node l v r) := (¬ lt a v  ¬ lt v a)  mem a l  mem a r

I am surprised it has not been done like this originally. Rather than traversing the left side of the tree first, it would be better to check immediately whether the element is a match and shortcut the search if it is.

Kenny Lau (Oct 19 2019 at 16:13):

or does not short circuit in Lean

Mario Carneiro (Oct 19 2019 at 16:17):

The implementation of rbmaps in core is... not good. You should probably not literally reuse it, but adapt it to your purposes if you like. There is an ordmap branch on mathlib with a faster implementation, but it's not completely proven correct yet

Mario Carneiro (Oct 19 2019 at 16:18):

@Kenny Lau , or does short circuit because of the decidable instance

Floris van Doorn (Oct 19 2019 at 17:36):

here is an example that shows or does short circuit:

#reduce if true  (@to_bool false $ classical.prop_decidable _) then 1 else 0

Marko Grdinić (Oct 20 2019 at 13:57):

prelude
import init.data.rbtree
import init.data.rbtree.basic tactic.library_search

universes u v w

def drbmap_lt {α : Type u} {β : α  Type v} (lt : α  α  Prop) (a b : Σ α, β α) : Prop :=
lt a.1 b.1

set_option auto_param.check_exists false

def drbmap (α : Type u) (β : α  Type v) (lt : α  α  Prop . rbtree.default_lt) : Type (max u v) :=
rbtree (Σ α, β α) (drbmap_lt lt)

def mk_drbmap (α : Type u) (β : α  Type v) (lt : α  α  Prop . rbtree.default_lt) : Type (max u v) :=
rbtree (Σ α, β α) (drbmap_lt lt)

namespace drbmap
variables {α : Type u} {β : α  Type v} {δ : Type w} {lt : α  α  Prop}

def empty (m : drbmap α β lt) : bool :=
m.empty

def to_list (m : drbmap α β lt) : list (Σ α, β α) :=
m.to_list

def min (m : drbmap α β lt) : option (Σ α, β α) :=
m.min

def max (m : drbmap α β lt) : option (Σ α, β α) :=
m.max

def fold (f :  (a : α), β a  δ  δ) (m : drbmap α β lt) (d : δ) : δ :=
m.fold (λ e, f e.1 e.2) d

def rev_fold (f :  (a : α), β a  δ  δ) (m : drbmap α β lt) (d : δ) : δ :=
m.rev_fold (λ e, f e.1 e.2) d

private def mem' (a : α) :  (m : rbnode (Σ α, β α)), Prop
| rbnode.leaf           := false
| (rbnode.red_node l  v, _   r)  := (¬ lt a v  ¬ lt v a)  mem' l  mem' r
| (rbnode.black_node l  v, _   r)  := (¬ lt a v  ¬ lt v a)  mem' l  mem' r

def mem (k : α) (m : drbmap α β lt) : Prop := @mem' α β lt k m.val

instance : has_mem α (drbmap α β lt) := mem

instance [has_repr (Σ α, β α)] : has_repr (drbmap α β lt) :=
⟨λ t, "drbmap_of " ++ repr t.to_list

def drbmap_lt_dec [h : decidable_rel lt] : decidable_rel (@drbmap_lt α β lt) :=
λ a b, h a.1 b.1

variable [decidable_rel lt]

def insert (m : drbmap α β lt) (k : α) (v : β k) : drbmap α β lt :=
@rbtree.insert _ _ drbmap_lt_dec m  k, v 

Here is what I did so far. However the find function is giving me some trouble.

private def find (k : α) : rbnode (Σ α, β α)  option (β k)
| rbnode.leaf             := none
| (rbnode.red_node a k' b) :=
    if h_lt : lt k k'.1 then find a
    else if h_gt : lt k'.1 k then find b
    else
        let x := some k'.2 in
        have h_eq : k'.1 = k, by sorry,
        by { rw h_eq at x, from x }
| (rbnode.black_node a k' b) :=
    if h_lt : lt k k'.1 then find a
    else if h_gt : lt k'.1 k then find b
    else
        let x := some k'.2 in
        have h_eq : k'.1 = k, by sorry,
        by { rw h_eq at x, from x }

I have that ~ k < k'.1 /\ ~ k'.1 < k, so I should be able to prove that k = k'.1, but I am not sure how to do that. Any idea of how I should approach this?

Marko Grdinić (Oct 20 2019 at 14:27):


def to_value : option (α × β)  option β
| none     := none
| (some e) := some e.2

The last issue that I have before I am done with generalizing the rbmap is to figure out what to do with to_value. Here is how it was originally. If the tuple is replaced by a sigma type then the definition should be something like the following...

def to_value : option (Σ α, β α) → option _

What should that wildcard be here? Is it even possible to do anything here? I think there probably isn't as the type of the value depends on the key now, but I am not completely sure so I'll ask here.

Marko Grdinić (Oct 21 2019 at 08:46):

With regard to my find questions, after some thinking I've come to the conclusion that there is no way that both sides of lt being false would mean that propositional equality must be true. lt can be anything and can for example compare only part of a key. So with that in mind, the right implementation would be...

private def find (k : α) : rbnode (Σ α, β α)  option (Σ α, β α)
| rbnode.leaf             := none
| (rbnode.red_node a k' b) :=
    if lt k k'.1 then find a
    else if lt k'.1 k then find b
    else k'
| (rbnode.black_node a k' b) :=
    if lt k k'.1 then find a
    else if lt k'.1 k then find b
    else k'

def find_entry (m : drbmap α β lt) (k : α) : option (Σ α, β α) := @find _ _ lt _ k m.val

With this finishing the rest of the module is easy.

def contains (m : drbmap α β lt) (k : α) : bool :=
(find_entry m k).is_some

def from_list (l : list (Σ α, β α)) (lt : α  α  Prop . rbtree.default_lt) [decidable_rel lt] : drbmap α β lt :=
l.foldl (λ m p, insert m p.1 p.2)  (mk_drbmap α β lt)

end drbmap

def drbmap_of {α : Type u} {β : α  Type v} (l : list (Σ α, β α)) (lt : α  α  Prop . rbtree.default_lt) [decidable_rel lt] : drbmap α β lt :=
drbmap.from_list l lt

Marko Grdinić (Oct 26 2019 at 08:54):

As it turns out, these red black trees not being able to understand that the key being put in is the same as the one being taken out when doing indexing into them is a lot bigger problem than I thought at first. With dependent types, that means that it can no longer reduce based on the key and it is causing me all sorts of issues downstream.

These trees are useless as they are now. I'd recommend avoiding them in favor of something that uses direct equality.

Marko Grdinić (Oct 26 2019 at 09:45):

Alternatively, it might be worth rewriting the above to use some kind of decidable trichotomy.


Last updated: Dec 20 2023 at 11:08 UTC