Zulip Chat Archive

Stream: general

Topic: bundled finite sets (and avoiding decidable instances)


Kyle Miller (May 13 2022 at 01:41):

Something that has been talked about many times before is having a variant on finset that's "for mathematics" (i.e., ignores issues of computability/decidability). There's a design with seemingly good definitional properties that's been rattling around my mind for a while that I spent some time today developing out to see how it might work.

Here's an overview of the design:

  • We keep finset as a data type for use in computation (and perhaps it is renamed to reflect its intended use and properties).
  • We replace set.finite with a predicate that says that there exists a finset with the same elements:
structure set.is_finite {α : Type*} (s : set α) : Prop :=
(exists_finset :  (t : finset α), s = t)
  • We define a bundled finite_set type implementing docs#set_like, and because all the finset business is in a Prop we can hide away computational issues using classical while making the carrier set have nice definitional properties.
/-- Bundled finite sets -/
structure finite_set (α : Type*) :=
(carrier : set α)
(finite : carrier.is_finite)
  • We replace fintype with a Prop-valued predicate, allowing us to avoid diamond problems:
class finite_type (α : Type*) : Prop :=
(univ_finite : (set.univ : set α).is_finite)
  • To work with unbundled finite sets, we use [finite_type s] just like how we are already using [fintype s], but now s.to_finite_set has the nice property that it definitionally coerces to s.
def set.to_finite_set {α : Type*} (s : set α) [finite_type s] : finite_set α :=
finite_set.mk s
(by { use (finite_set.univ : finite_set s).finite.to_finset.map (function.embedding.subtype _),
      ext, simp })

@[simp] lemma set.coe_to_finite_set {α : Type*} (s : set α) [finite_type s] :
  (s.to_finite_set : set α) = s := rfl
  • We can still talk about computation by introducing instances for computing specific sets as finsets. We can compute specific finite_sets too by giving has_finset instances for them (since they coerce to set).
/-- Class for computing a `finset` for a `set`. -/
class has_finset {α : Type*} (s : set α) :=
(to_finset : finset α)
(eq_to_finset [] : s = to_finset)

Kyle Miller (May 13 2022 at 01:41):

(code below)

Kyle Miller (May 13 2022 at 01:47):

For defining operations on finite_set, the pattern is to

  1. Define a set.is_finite constructor for the given set, for instance
protected lemma is_finite.inter {s t : set α} (hs : s.is_finite) (ht : t.is_finite) :
  (s  t).is_finite :=
by { classical, exact ⟨⟨hs.to_finset  ht.to_finset, by { ext, simp }⟩⟩ }

This involves giving a corresponding finset construction.

  1. Define the finite_set operation, for instance
instance : has_inter (finite_set α) := λ s t, s  t, set.is_finite.inter s.finite t.finite⟩⟩

This is simply a matter of bundling the is_finite operation.

  1. Provide extensionality lemmas, for instance
@[simp] lemma mem_inter {s t : finite_set α} {x : α} : x  s  t  x  s  x  t := iff.rfl

Kyle Miller (May 13 2022 at 01:51):

For lifting operations on sets to computations on finsets, then one can also define some instances for set operations.

instance {s t : set α} [has_finset s] [has_finset t] [decidable_eq α] :
  has_finset (s  t) := s.get_finset  t.get_finset, by simp

The has_finset class is the set/finset analogue to decidable_eq, which is for Prop/Bool. I don't really know how well this class works in practice, but using instances seems like a reasonably good place to record this sort of thing.

Kyle Miller (May 13 2022 at 02:00):

I guess these has_finset instances can actually be useful for defining is_finite constructors.

protected lemma is_finite.inter {s t : set α} (hs : s.is_finite) (ht : t.is_finite) :
  (s  t).is_finite :=
by { classical, haveI := hs.has_finset, haveI := ht.has_finset, apply is_finite_of_has_finset }
-- ^ code that's not specific to the situation.

Kyle Miller (May 13 2022 at 02:21):

newest code

Yaël Dillies (May 13 2022 at 08:21):

Why not go the other way around and hold a set in finset?

structure finset (α : Type*) :=
(val : multiset α)
(nodup : nodup val)
(to_set : set α)
(coe_eq :  x, x  val  to_set)

Then you can define the coercion from finset α to set α as finset.to_set. No need for a new structure, and not much changes.

Kyle Miller (May 13 2022 at 11:45):

That still needs decidable instances to be useful as data. The design goals are (1) to keep a computational finite set definition around, (2) to have a computationally irrelevant finite set definition (no more decidable instances in proofs about mathematical finite sets!), (3) to have this definition have good definitional properties with respect to set, (4) to have fintype be a Prop so that instances may enjoy proof irrelevance, and (5) to have a way to associate computational sets to computationally irrelevant ones (so we at least have a place to put fintype algorithms).

That suggested change to finset partly handles 3 and does not simultaneously handle 1 and 2. The issue with 3 is that even if the sets are definitionally equal, the multisets might not be.

Yaël Dillies (May 13 2022 at 11:46):

Of course, this is a trade-off. But experience suggests that we care much more about the finset α → set α coercion than the finset α → multiset α one.

Yaël Dillies (May 13 2022 at 11:49):

I'm afraid this will just end up being "Yet another way to talk about finite things" while the symptom of the problem hasn't been solved. Namely, decidability shouldn't stop defeq. A few uneducated solutions I have to this are:

  • Do not handle decidability with instances. An option is your compuitable typeclass. Another one would to have a fully separate decidability handler, which automatically marks declaration with the right decidability instances.
  • Make subsingleton elimination defeq, at least for decidable and fintype

Eric Rodriguez (May 13 2022 at 13:13):

Make subsingleton elimination defeq, at least for decidable and fintype

I've always suggested this, or at least a type-class inference system that can handle defeq. I feel like the deeper we get into maths, the more we can't rejig to make things defeq. I often struggle accepting that x^1 is not definitionally equal to x (or hell, even 1*x or x*1), and these issues are just going to keep intensifying, c.f. representation theory

Eric Wieser (May 13 2022 at 13:16):

I'm curious if the change in #14122 will make decidable arguments in proofs less annoying

Eric Rodriguez (May 13 2022 at 13:19):

I guess this will mostly solve decidable, but no good solution to fintype still :(

Eric Rodriguez (May 13 2022 at 13:19):

And the only real reason it solves is it because no-one ever writes decidable instances ;b

Reid Barton (May 13 2022 at 13:39):

Make subsingleton elimination defeq, at least for decidable and fintype

I think it's strictly better to just make decidable and fintype propositions, mainly because it's a thing we can actually do

Eric Wieser (May 13 2022 at 13:40):

Making decidable p : Prop would be pointless (and would make if noncomputable!), so I assume you meant something else

Reid Barton (May 13 2022 at 13:40):

Well, effectively it means getting rid of (i.e., not using) decidable

Reid Barton (May 13 2022 at 13:41):

(Also, it's not constructively pointless!)

Eric Wieser (May 13 2022 at 13:41):

decidable is still needed for meta code though

Reid Barton (May 13 2022 at 13:41):

(Also, it's not constructively pointless!)

Reid Barton (May 13 2022 at 13:41):

Well, effectively it means getting rid of (i.e., not using) decidable

Reid Barton (May 13 2022 at 13:41):

Right, if should be noncomputable

Eric Wieser (May 13 2022 at 13:41):

Reid Barton said:

Right, if should be noncomputable

Then we need a different if for meta code

Reid Barton (May 13 2022 at 13:42):

If you're going down the extensional type theory road then you are going to have way bigger problems than that

Reid Barton (May 13 2022 at 14:01):

The general case is that if you want a definitionally irrelevant subsingleton then we already have it, it's called Prop. I agree including the case of decidable is a bit silly.

Eric Wieser (May 13 2022 at 14:06):

Does the type theory become more complex if we allow trunc to be definitionally irrelevant too?

Eric Wieser (May 13 2022 at 14:07):

Because we could wrap everything where this problem arises in trunc if necessary

Reid Barton (May 13 2022 at 14:09):

I'm pretty sure all of these amount to equality reflection, i.e., if you have h : a = b then a and b are defeq

Reid Barton (May 13 2022 at 14:10):

except the ones involving decidable, which I'm not sure about

Eric Rodriguez (May 13 2022 at 14:11):

what was the issue with equality reflection again?

Reid Barton (May 13 2022 at 14:13):

It's way outside the scope of Lean. I've seen a theorem prover based on extensional type theory but I don't remember what it was called, or how serious it was

Anne Baanen (May 13 2022 at 14:17):

NuPRL has extensional types I believe.

Reid Barton (May 13 2022 at 14:19):

The main issue is that to check a defeq the kernel might have to synthesize some arbitrary proof of equality--obviously there's no algorithm that can do so in all cases, so how do you make something usable and reliable and trustable.

Reid Barton (May 13 2022 at 14:20):

In Lean's current type theory the definitional equality is more or less decidable by an algorithm and that algorithm works in practice most of the time.

Eric Rodriguez (May 13 2022 at 14:20):

why can't we just use the current kernel typechecker. if a rfl that should work fails that shouldn't be able to affect consistency

Eric Rodriguez (May 13 2022 at 14:21):

and then we can just add the extra rfls that need kernel support

Reid Barton (May 13 2022 at 14:22):

Those extra rfls are where the problem is

Reid Barton (May 13 2022 at 14:22):

Those extra rfls are where the problem is

Eric Rodriguez (May 13 2022 at 14:25):

how so? say for example we have Lean + "if there's a proof that a type t is a subsingleton in the instance cache somehow, then a = b : @eq t is rfl"

Reid Barton (May 13 2022 at 14:28):

Well the kernel doesn't have an instance cache

Reid Barton (May 13 2022 at 14:31):

Proof irrelevance for Prop is already a bit funny and I think it's why definitions using well-founded recursion don't compute in the kernel (right?)

Eric Rodriguez (May 13 2022 at 14:31):

Hmm, I guess not, but the higher-level automation could add the instances that are used using have statements to the proofs. I guess this is really slow and complicated, though

Eric Rodriguez (May 13 2022 at 14:32):

For specific types, however, specific support could be added and that solves 99% of the issues (fintype, decidable, Q-algebras maybe)

Johan Commelin (May 13 2022 at 14:35):

Eric Wieser said:

Then we need a different if for meta code

I suggest mif

Reid Barton (May 13 2022 at 14:38):

OK so let's suppose you write down a rule "if a : fintype X and b : fintype X then a and b are defeq"

Reid Barton (May 13 2022 at 14:38):

How do you know when to apply the rule?

Kyle Miller (May 13 2022 at 14:39):

@Johan Commelin That makes sense, but we should probably think ahead to Lean 4 -- we probably don't want to override its programming language features. We could have if' on the math side, or cif for "classical if".

Reid Barton (May 13 2022 at 14:39):

Maybe I'm trying to type check rfl : 0 = sum (x : X), x

Reid Barton (May 13 2022 at 14:41):

That sum involves a fintype X argument which might be stuck for some reason (e.g., uses an axiom). But wait! what if I can somehow cook up a second b : fintype X which isn't stuck, and then keep reducing

Reid Barton (May 13 2022 at 14:41):

Now I am back to the same problem, namely, I should be able to synthesize terms out of thin air

Reid Barton (May 13 2022 at 14:44):

Maybe you say: well too bad, I don't know how to do that. Then you are in the nontransitivite defeq situation. The sum with the bad fintype instance is convertible to the one with the good fintype instance (by fintype irrelevance), which can be reduced to 0. But we aren't capable of reducing the original sum to 0.

Reid Barton (May 13 2022 at 14:46):

If that is going to happen anyways, then maybe sum should just be noncomputable in the first place, and we can use tactics/propositional reasoning when we want to "compute" it. At least this is something that we could Just Do today, and doesn't require modifying the kernel with possibly other consequences.

Eric Rodriguez (May 13 2022 at 14:48):

Thanks, that's a really good explanation. I feel like a non-transitive defeq isn't that evil, though - hell, I thought we had it already for some language features. Plus, it also fits my idea of "true by definition" in informal mathematics.

Eric Rodriguez (May 13 2022 at 14:49):

But I guess this second approach is the current tsum/etc approach, and that leaves us with a lot of arguments we have to carry around. Maybe we can improve that by making an instanced version of nonempty (x)for relevant x, and then we get TC search and not a bunch of annoying arguments everywhere

Kyle Miller (May 13 2022 at 14:50):

Yaël Dillies said:

Of course, this is a trade-off. But experience suggests that we care much more about the finset α → set α coercion than the finset α → multiset α one.

I suspect I'm not being understood here. The issues is not about coercions per se. The proposed finite_set type has the property that the finite sets are definitionally equal if and only if the underlying sets are definitionally equal. When you have the multiset in the mix as actual data, you essentially have diamond problems, which I at least would like to avoid.

Regarding a trade-off, the five design goals I mentioned are all met by the finite_set proposal. It does not meet the unmentioned number 6 (not be a whole lot of work) but I think this is unavoidable.

So long as we keep using data structures for finite sets as if they were sets that are finite, I think we're going to keep having a struggle. Regarding your concern about "yet another way to talk about finite things", it seems similar to how we have both lists and functions fin n -> A, but we don't try to force one to be the other.

Reid Barton (May 13 2022 at 14:50):

Or maybe you really specifically need the definitional equality (because these expressions are appearing in the index of some type family) and then yes, I agree it would be much nicer if you could convince the kernel to do these conversions; I just don't know what the framework for making it work reliably should look like, and I'm not sure whether anyone does

Kyle Miller (May 13 2022 at 14:51):

@Reid Barton Do you have any thoughts about the finite_set proposal? It seems like you're in favor at least in principle. I'm wondering partly because I know you spent time a while back working on this sort of thing.

Kyle Miller (May 13 2022 at 14:52):

To reduce work, I think leaving fintype as-is makes sense for now (rather than replacing it) and introducing finite_type for the Prop version, then having finite_type instances from fintype instances.

Eric Wieser (May 13 2022 at 14:55):

Yaël Dillies said:

Why not go the other way around and hold a set in finset?
<snip>

Just to note, the effect of this change is to have nicer definitional control of what finset membership means, but it's orthogonal to decidability discussions.

Kyle Miller (May 13 2022 at 15:02):

@Eric Wieser What's "this"? I assume you're clarifying Yael's suggestion?

Eric Wieser (May 13 2022 at 15:03):

"this" refers to Yael's suggestion in the message I quoted

Kyle Miller (May 14 2022 at 16:22):

After going through all of what we have already in data/set/finite and reorganizing it (#14136), I guess we already have pretty much everything set up for bundled finite sets. Here's an overview for a proposed design:

  • finset remains as-is. It's the computational model (i.e. a data structure) for finite sets.
  • fintype remains as-is. Paired with docs#set.to_finite, this is the database of ways to turn sets that are finite into computable finite sets. (It's the finite set analogue of decidable.)
  • set.finite remains as-is. This is the predicate that represents the mere fact that a set is finite without offering any computational guarantees.
  • In data/set/finite there is a long list of fintype instances that give ways of computing different set constructions already.
  • Parallel to that, data/set/finite also has a long list of set.finite constructions. These are mostly formulaic, just applying fintype instances.
  • Then we can define finite_set, which is a set that is set.finite. This will have all the properties I mentioned in my first posts. Operations on finite_set can be lifted in a mostly formulaic way from corresponding set.finite constructions, and properties should be mostly liftable from set properties due to the fact a finite_set "is" a set. (Note: in the VM, a finite_set will be essentially computationally irrelevant, similar to set. At most they'll be passed around as zero-argument constructors.)
  • We can also define finite_type. There are a couple ways this could be defined, but probably the easiest is nonempty (fintype A). This is a type whose set.univ can be given as a finite_set.

Kevin Buzzard (May 14 2022 at 17:15):

+1 for finite_type, I thought @Yury G. Kudryashov mentioned at some point that he was thinking of adding this.

Yury G. Kudryashov (May 14 2022 at 17:17):

I've started working on a typeclass for countable types (and sorts), see branch#YK-countable.

Yury G. Kudryashov (May 14 2022 at 17:17):

It would be nice if someone adopts this branch.

Yury G. Kudryashov (May 14 2022 at 17:17):

About finite_type: please make it work for Sort*.

Yury G. Kudryashov (May 14 2022 at 17:18):

E.g., you can define finite_type (α : Sort*) := (ex_equiv_fin : ∃ n : nat, nonempty (α ≃ fin n))

Yury G. Kudryashov (May 14 2022 at 17:19):

With this typeclass, we can have lemmas about finite unions/intersections without dealing separately with unions over proofs of propositions.

Kyle Miller (Jun 03 2022 at 22:20):

I was wondering all the ways we have to represent finite (multi)sets in mathlib. Here's what I could think of (and if there are others anyone can think of, I'll add them to the list).

Finite sets:

Finite multisets:

Kyle Miller (Jun 03 2022 at 22:22):

This is a kind of funny and perhaps roundabout way to work with finite sets:

import data.finsupp.basic

instance : has_zero Prop := false

def finset' (α : Sort*) := finsupp α Prop

def finset'.to_set {α : Type*} : finset' α  set α := finsupp.to_fun

If you expand out the definitions it's essentially something Yael suggested earlier, which is a finite set definition that contains both a finset and a set so you can get definitional control over both.


Last updated: Dec 20 2023 at 11:08 UTC