Zulip Chat Archive

Stream: lean4

Topic: Mutual definition with axiom / forward declaration


bruno cuconato (Feb 28 2024 at 15:53):

Hello,

I am trying to write type whose constructor includes a predicate on this type, but I do not want to go into the definition of this predicate, I take it as given. In Agda I can write

data MyType : Set

postulate
  MyPredicateOnType : MyType  MyType  Set

data MyType where
    init : MyType
    cons : (e1 e2 : MyType)  MyPredicateOnType e1 e2  MyType

But I fail to write the Lean equivalent, as Lean does not seem to support this kind of forward declaration (see this thread https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/forward.20declarations), and I do not seem to be able use an axiom or an opaque definition in a mutual block.

A workaround is defining two types, with the first one not including the predicate, whereas the second one would include it, something like

inductive MyType1 : Type where
| init : MyType1
| cons : MyType1  MyType1  MyType1

axiom MyPredicateOnType : MyType1  MyType1  Prop

inductive MyType2 : Type where
| init : MyType1  MyType2
| cons : (e1 e2 : MyType1)  MyPredicateOnType e1 e2  MyType2

But I'd prefer a clear solution. Is there any? Thank you!

Timo Carlin-Burns (Feb 28 2024 at 15:58):

I think the standard approach here is approximately what you've proposed:

  1. Define the inductive type without restricting to any predicates
  2. Define a recursive validity predicate on the initial inductive type
  3. Use the subtype defined by the validity predicate instead of the base type thereafter

The Lean 4 manual has an example of this for a binary search tree data structure

Timo Carlin-Burns (Feb 28 2024 at 16:02):

Note that MyType2 as you've defined it above isn't exactly what you want: it only ensures that the outermost cons-pair satisfies MyPredicateOnType, not that every cons-pair does so recursively

Timo Carlin-Burns (Feb 28 2024 at 16:06):

I doubt the agda behavior here was sound. If you just want to emulate what you were doing in Agda and bypass Lean's soundness checks, I imagine that one of your original ideas would work with unsafe inductive

bruno cuconato (Feb 28 2024 at 18:15):

Thank you @Timo Carlin-Burns! That helps. Out of curiosity, do you perchance have an example of how this Agda behaviour can lead to an unsoundness?

Timo Carlin-Burns (Feb 28 2024 at 21:21):

Lean has a number of limitations on inductive type definitions that mostly boil down to something called "strict positivity". "strict positivity" has a precise definition in Theorem Proving in Lean but basically means that you only refer to your type within its own definition in certain positions:

  • As the return type of a constructor
  • As an argument to a constructor
  • As the return type of a function which is an argument to a constructor

So e.g. the following is fine:

inductive MyInfiniteTree : Type where
| leaf : MyInfiniteTree
| node : (Nat -> MyInfiniteTree) -> MyInfiniteTree -- each node has infinite children
--               ^ occurs as return type, not input type, of a function

An example which is not fine is

inductive Bad : Type where
| ofFn : (Bad -> Bool) -> Bad

Here ofFn is a constructor, and all constructors are injective, so we have an injective function from (Bad -> Bool) -> Bad which is impossible by cantor's diagonal argument.

I don't know how to construct an example where you would try to define a type and a predicate on it in a mutual inductive fashion and it would lead to inconsistency, but in general, the strict positivity check means you're not really supposed to use your type as an input to anything while you're defining it.

bruno cuconato (Mar 01 2024 at 20:30):

Thanks for the explanation. Agda does not accept this Bad example, but I guess it accepts my former example because the expression MyPredicateOnType e1 e2 has type Set. So I don't see why the strict positivity check would be a problem here, and thus I am led to think (maybe wrongly, I'll gladly take further corrections/explanations!) that Lean would accept my Lean code if I could forward declare MyPredicateOnType or mutually define it.

Floris van Doorn (Mar 01 2024 at 22:03):

I believe Agda supports both inductive-inductive datatypes and inductive-recursive datatypes. Lean accepts neither of them.

Your example is an inductive-inductive datatype (at least if MyPredicateOnType is data instead of a postulate). You can encode these in Lean, see for example this thread. Inductive-recursive datatypes significantly increase the proof-theoretic strength of a system, hence cannot be encoded in Lean in general.

Mario Carneiro (Mar 02 2024 at 05:55):

@bruno cuconato Here's a basic example of issues that could come up if you aren't careful what is allowed in the definition of MyPredicateOnType:

mutual

inductive MyType : Type where
| init : MyType  MyType
| cons : (e1 e2 : MyType)  MyPredicateOnType e1 e2  MyType

def MyPredicateOnType : MyType  MyType  Prop :=
  fun a b =>  e h, e = MyType.cons a b h  False

end

This is only a lightly obfuscated version of the liar paradox:

def MyPredicateOnType : MyType  MyType  Prop :=
  fun a b => ¬ MyPredicateOnType a b

which causes inconsistency in any system that allows the definition

bruno cuconato (Mar 14 2024 at 13:19):

Thank you @Floris van Doorn and @Mario Carneiro ! That helped me understand why I should follow the Lean modus operandi and do something similar to the BST example Timo linked. I don't actually need inductive-inductive or inductive-recursive datatypes for what I'm doing after all :)


Last updated: May 02 2025 at 03:31 UTC