Zulip Chat Archive

Stream: lean4

Topic: Proper way to implement conditional decidability


Mario Weitzer (May 19 2025 at 14:24):

Let's say I have a type α for which there is a predicate isFinite which is decidable and another predicate p which is decidable if isFinite holds. What is the best way to implement this such that type class inference works without much overhead?

Edward van de Meent (May 19 2025 at 14:27):

i don't know about overhead, but that sounds to me like you want an assumption [Decidable (IsFinite α)] to your instance?

Floris van Doorn (May 19 2025 at 14:30):

Do you mean something like this?

variable {α : Type}
def isFinite : α  Prop := sorry

instance : DecidablePred (isFinite : α  Prop) := sorry

def anotherPred : α  Prop := sorry

instance [DecidablePred (isFinite : α  Prop)] : DecidablePred (anotherPred : α  Prop) := sorry

If not, it is useful if you provide a #mwe with what you want.

Mario Weitzer (May 19 2025 at 14:32):

Yes, I think that's exactly what I am looking for. Thanks to both of you!

Eric Wieser (May 19 2025 at 14:38):

That says that anotherPred is globally decidable if isFinite is globally decidable, not that one is decidable only where the other is

Mario Weitzer (May 19 2025 at 14:39):

No wait, sorry, that's not what I want. I want to say that anotherPred is only decidable for those elements of α for which isFinite is true.

Aaron Liu (May 19 2025 at 14:41):

How is typeclass supposed to know at compile-time that your element of α is finite?

Eric Wieser (May 19 2025 at 14:42):

I think there are hacks to make typeclass search run decide

Mario Weitzer (May 19 2025 at 14:52):

Here is a toy example:

import Mathlib.Data.ENat.Basic

structure Sequence.{u} (α : Type u) where
  length : 
  entries :   α

----------------------------------------------------------------------------------------------------

universe u

variable {α : Type u}

namespace Sequence

def isFinite (S : Sequence α) : Prop :=
  S.length  

class IsFinite (S : Sequence α) : Prop where
  out : isFinite S

instance isFinite_DecPred : DecidablePred (@isFinite α) :=
  λ S =>
  match decEq S.length  with
  | isTrue hi  => isFalse (λ hni => hni hi)
  | isFalse hni => isTrue (λ hi => hni hi)

def S : Sequence  := 5, λ i => i
def T : Sequence  := ⟨⊤, λ i => i

example : isFinite S := by decide
example : ¬(isFinite T) := by decide

def contains (a : α) (S : Sequence α) : Prop :=
   i, i < S.length  S.entries i.toNat = a

--What I want:
instance contains_DecPred (a : α) : DecidablePred (@contains α a) "if isFinite S" :=
  sorry

--The clumsy solution I found so far:
instance contains_DecPred' (a : α) :
  DecidablePred (λ S : { S : Sequence α | isFinite S } => @contains α a S) := by
  sorry

What is considered best practice in such a situation?

Robin Arnez (May 20 2025 at 20:50):

just

instance (a : α) (S : Sequence α) [IsFinite S] : Decidable (contains a S) :=

that's basically how DecidablePred is defined

Mario Weitzer (May 21 2025 at 08:02):

Yes, I added this instance in my case but in some situations (when using decide for example) Lean complains that "failed to synthesize Decidable (contains a S)" even if I explicitly add a proof of IsFinite S to the context (which I shouldn't even need since IsFinite is a decidable predicate). In other situations Lean complains that contains is not a decidable predicate. The solution I found so far is

instance (a : α) : DecidablePred (λ S : Sequence α => IsFinite S  contains a S) := sorry

lemma IsFinite_imp {S : Sequence α} {p : Sequence α  Prop} (hf : IsFinite S) (hp : IsFinite S  p S) :
  p S := hp hf

lemma not_IsFinite_imp {S : Sequence α} {p : Sequence α  Prop} (hp : ¬(IsFinite S  p S)) :
  ¬p S := λ hps => hp (λ _ => hps)

def x := some finite sequence which contains some a
def y := some finite sequence which doesn't contain some a

example : contains a x := @IsFinite_imp _ _ _ (by decide) (by decide)

example : ¬contains a y := not_IsFinite_imp (by decide)

interestingly

example : contains a x := IsFinite_imp (by decide) (by decide)

doesn't work even though I don't give any additional information in

example : contains a x := @IsFinite_imp _ _ _ (by decide) (by decide)

Robin Arnez (May 21 2025 at 08:25):

Mario Weitzer schrieb:

which I shouldn't even need since IsFinite is a decidable predicate

Lean won't do any computational effort to figure out whether it could be a finite sequence when you synthezize IsFinite and in particular, defs are opaque to instance synthesis. So what you'd need is an instance and to declare your sequences as abbrevs:

class EqInst (a : α) (b : outParam α) where
  out : a = b

instance : EqInst a a := rfl

instance (y : Nat) [h : EqInst x.1 (y : ENat)] : IsFinite x := by
  rcases x with l, _⟩
  rcases h with rfl
  constructor
  simp [isFinite]

def contains (a : α) (S : Sequence α) : Prop :=
   i, i < S.length  S.entries i.toNat = a

--What I want:
instance [DecidableEq α] (a : α) (S : Sequence α) [h : IsFinite S] : Decidable (contains a S) :=
  decidable_of_iff ( i, i < S.length.toNat  S.entries i = a) sorry

abbrev x : Sequence Nat := { length := 3, entries | 0 => 25 | 1 => 17 | _ + 2 => 99 }
abbrev y : Sequence Nat := { length := 5, entries | 0 => 19 | 1 => 24 | 2 => 54 | 3 => 9 | _ + 4 => 88 }

example : contains 17 x := by decide
example : ¬contains 17 y := by decide

(something similar to EqInst might be in mathlib, not sure)

Robin Arnez (May 21 2025 at 08:30):

An alternative would be to use Classical.propDecidable in the branches where it isn't possible otherwise:

noncomputable instance contains_DecPred [DecidableEq α] (a : α) (S : Sequence α) : Decidable (contains a S) :=
  if h : S.length =  then
    Classical.propDecidable _
  else
    decidable_of_iff ( i, i < S.length.toNat  S.entries i = a) sorry

(this solution actually also works without abbrev)

Mario Weitzer (May 21 2025 at 08:33):

These are great solutions, thank you!


Last updated: Dec 20 2025 at 21:32 UTC