Zulip Chat Archive

Stream: Is there code for X?

Topic: Is there a non-repeating list in Init or Std?


Schrodinger ZHU Yifan (Sep 12 2023 at 17:44):

Is there a lean version of NonRepeating list existing in Init or Std?

open import Relation.Nullary
open import Relation.Binary.PropositionalEquality
open import Data.List
open import Data.List.Relation.Unary.Any

data NonRepeating {a} {A : Set a} : (l : List A)  Set a where
  done : NonRepeating []
  rest :  {x xs}  ¬ Any (x ≡_) xs  NonRepeating xs  NonRepeating (x  xs)

record UniqueList {a} (A : Set a) : Set a where
  constructor _//_
  field
    l : List A
    wf : NonRepeating l

Mario Carneiro (Sep 12 2023 at 17:47):

not as a single type, but you can use List A and l.Nodup

Mario Carneiro (Sep 12 2023 at 17:48):

if you quotient up to permutation you get Finset A

Schrodinger ZHU Yifan (Sep 12 2023 at 20:35):

I end up with writing something:

inductive NoDup : List α  Prop
  | nil : NoDup []
  | cons a l : α  l  NoDup l  NoDup (a :: l)

abbrev NoDupList α := @Subtype (List α) NoDup

def t : NoDupList Nat  :=  [1, 2, 3], by
  apply NoDup.cons 1 [2, 3] (by simp)
  apply NoDup.cons 2 [3] (by simp)
  apply NoDup.cons 3 [] (by simp)
  apply NoDup.nil

However, it is a little bit painful to use as lean cannot automatically give the proof of NoDup. ( you cannot writedef t : NoDupList Nat := ⟨ [1, 2, 3], by simp ⟩).

Is it possible to write a macro to generate the code? Could you please show me an example?

def t NoDupList Nat := NoDupList![1, 2, 3]
-- generates =>
def t : NoDupList Nat  :=  [1, 2, 3], by
  apply NoDup.cons 1 [2, 3] (by simp)
  apply NoDup.cons 2 [3] (by simp)
  apply NoDup.cons 3 [] (by simp)
  apply NoDup.nil

Kevin Buzzard (Sep 12 2023 at 20:57):

docs#List.Nodup is there already

Schrodinger ZHU Yifan (Sep 12 2023 at 21:26):

I see, it actually has by decide

Eric Wieser (Sep 12 2023 at 21:48):

{l : List α // l.Nodup} would be the canonical spelling I think

Schrodinger ZHU Yifan (Sep 13 2023 at 17:40):

How can I achieve something similar to (the following is rejected):

import Std.Data.List.Basic
open Std
inductive X :=
  | A
  | B (xs : { l: List (String × X) // l.map (·.1) |>.Nodup })

Patrick Massot (Sep 13 2023 at 17:53):

Interesting riddle. Minimized version:

import Std.Data.List.Basic
open Std

-- Works
inductive X' :=
  | A : X'
  | B : (l: List X')  X'

-- Fails
inductive X :=
  | A : X
  | B : (l: List X)  (hl : l.length < 3)  X

Adam Topaz (Sep 13 2023 at 17:56):

(deleted)

Adam Topaz (Sep 13 2023 at 17:57):

that wasn't the intended type, sorry

Eric Wieser (Sep 13 2023 at 19:38):

FWIW, l.map (·.1) |>.Nodup is effectively docs#List.NodupKeys

Eric Wieser (Sep 13 2023 at 19:41):

To save someone loading up Lean, Patrick's example fails with:

application type mismatch
  List.length l
argument has type
  _nested.List_1
but function has type
  List X  Nat

Schrodinger ZHU Yifan (Sep 13 2023 at 21:16):

Also, the following won't work.

import Mathlib.Data.List.AList

inductive X :=
  | A
  | B (xs : AList (α := String) (fun _ => X))

Eric Wieser (Sep 13 2023 at 21:56):

I think this is just a bug

Eric Wieser (Sep 13 2023 at 21:57):

Certainly it's a bug that _nested is appearing in the error message

Eric Wieser (Sep 13 2023 at 21:57):

I think it would be appropriate to file a Lean4 issue on github here

Mario Carneiro (Sep 13 2023 at 22:01):

I don't think so, AList is a subtype, which requires evaluating NodupKeys on the nested inductive type List (String x X)

Mario Carneiro (Sep 13 2023 at 22:02):

if you inline everything you will see it can't be defined

Mario Carneiro (Sep 13 2023 at 22:03):

this has approximately the same error:

inductive X :=
  | A
  | B
    (entries : List (Σ _:String, X))
    (nodupKeys : entries.NodupKeys)

commenting out the last line makes it work

Schrodinger ZHU Yifan (Sep 13 2023 at 22:04):

emmm, but you can do something like:

inductive X :=
  | private A' : X
  | private B' : (l: List α)  (hl : l.length < 3)  X

def X.B := X.B' (α := X)
def X.A := X.A'

Mario Carneiro (Sep 13 2023 at 22:06):

that's not the same thing, X.B' still quantifies over all types α

Mario Carneiro (Sep 13 2023 at 22:06):

note that X is universe bumped because of this

Mario Carneiro (Sep 13 2023 at 22:07):

An inductive type has constructors and also a recursor. Just because you define a more specific constructor wrapper doesn't mean the recursor is also made more specific

Mario Carneiro (Sep 13 2023 at 22:08):

if you did recursion on X you would have to deal with arbitrary other types α in the B' case

Eric Wieser (Sep 13 2023 at 22:09):

Mario Carneiro said:

if you inline everything you will see it can't be defined

In that case, the lean4 bug is "the error message should say this can't be done, and not just reveal some error in an implementation detail"

Eric Wieser (Sep 13 2023 at 22:10):

Is there a way to get at the "inlined" version that uses this _nested.List_1?

Mario Carneiro (Sep 13 2023 at 22:34):

No, I think that definition never actually exists in the environment

Mario Carneiro (Sep 13 2023 at 22:35):

It's hard to see much about it because it only ever shows up in error messages

Mario Carneiro (Sep 13 2023 at 22:40):

but the gist of it is that when compiling a nested inductive type, lean has to make copies of all inductive types involved in the "cycle" while creating the type of the recursor. You can see a bit of this in the simplified inductive:

inductive X :=
  | A
  | B
    (entries : List (Σ _:String, X))

#print X.rec
recursor X.rec.{u} : {motive_1 : X  Sort u} 
  {motive_2 : List ((_ : String) × X)  Sort u} 
    {motive_3 : (_ : String) × X  Sort u} 
      motive_1 X.A 
        ((entries : List ((_ : String) × X))  motive_2 entries  motive_1 (X.B entries)) 
          motive_2 [] 
            ((head : (_ : String) × X) 
                (tail : List ((_ : String) × X))  motive_3 head  motive_2 tail  motive_2 (head :: tail)) 
              ((fst : String)  (snd : (fun x  X) fst)  motive_1 snd  motive_3 { fst := fst, snd := snd }) 
                (t : X)  motive_1 t

Here there are 3 motives for 3 mutually inductive types, which you might call X, List_Sigma_String_X and Sigma_String_X. During post-processing, these types are replaced with the types they are supposed to be standing in for, namely X, List ((_ : String) × X) and (_ : String) × X respectively.

The trouble is that the type List_Sigma_String_X created during this process isn't actually List _, so if you try to apply a function like List.NodupKeys it will be a type error. In lean 3 this type would actually stick around and you could see it in the reduction, but in lean 4 it disappears after elaboration so you just have a reference to a type _nested.List_1 that doesn't exist

Eric Wieser (Sep 13 2023 at 22:44):

I think the thing I was struggling with is why lean can't insert some conversion function between the temporary list and the real one; but I guess the answer is that that would amount to using a recursor of a type within its own definition which isn't allowed

Schrodinger ZHU Yifan (Sep 13 2023 at 22:44):

I am not sure what‘s agda’s vec equivalence in lean. but with that,I guess I can do it a different way like:

B takes an implicit (n : nat), then separate list into two finite vec, associated with some prop on the first vec of string.

Emmm, but it seems tedious to use.

Schrodinger ZHU Yifan (Sep 13 2023 at 22:46):

Oh, no it won’t. Vec does not give the same length. Only an upper bound.

Mario Carneiro (Sep 13 2023 at 22:48):

Eric Wieser said:

I think the thing I was struggling with is why lean can't insert some conversion function between the temporary list and the real one; but I guess the answer is that that would amount to using a recursor of a type within its own definition which isn't allowed

Right, this gets into inductive-recursive types if you want to do this (which are supported in agda but are strictly stronger in proof strength than what lean supports)

Mario Carneiro (Sep 13 2023 at 22:49):

it's not even about having the conversion function, having any function there which inspects the type nontrivially (like List.length) is already recursion

Eric Wieser (Sep 13 2023 at 22:49):

What type of inspection is trivial?

Mario Carneiro (Sep 13 2023 at 22:50):

Function.const

Eric Wieser (Sep 13 2023 at 22:50):

That's a pretty dubious meaning of inspect

Eric Wieser (Sep 13 2023 at 22:50):

It would be nice if the error message included the words "inductive-recursive types are not supported"

Mario Carneiro (Sep 13 2023 at 22:50):

that is, if you have some function which would type check even if the argument had type x : A for some variable type A

Eric Wieser (Sep 13 2023 at 22:52):

Would a custom elaborator work to build such a "function", or would that be elaborated fully before making the substitution?

Mario Carneiro (Sep 13 2023 at 22:52):

not sure what you mean

Schrodinger ZHU Yifan (Sep 14 2023 at 01:00):

Back to the problem, I think to achieve the constrains, one can do something like:

inductive Vec (α : Type) : Nat  Type :=
  | nil  : Vec α 0
  | cons : α  Vec α n  Vec α (n+1)

def Vec.NoDup (x : Vec α n) : Prop := sorry

inductive X : Type :=
  | A : X
  | B : {n : Nat}  {v : Vec String n // v.NoDup }  Vec X n  X

-- Mathlib version won't work
-- inductive X' : Type :=
--  | A : X'
--  | B : {n : Nat} → {v : Vector String n // v.val.Nodup } → Vector X' n → X'

I am not sure whether there is a better way to do this.


Last updated: Dec 20 2023 at 11:08 UTC