Zulip Chat Archive
Stream: new members
Topic: Mutually recursive inductive types
Sam Stites (Apr 14 2020 at 15:11):
Hi all,
I'm trying to use Lean to do some language design and experimentation.
I'm just starting with an untyped lambda calculus which looks a little like:
constants (real : Type) structure var := (Var : string) structure fn := (Fn : string) inductive primval : Type | Real : real → primval | Bool : bool → primval | MyList : list myexpr → primval inductive primop : Type | Op : string → primop -- number ops | Mul : primop | Sub : primop | Div : primop | Add : primop | GT : primop | LT : primop -- bool ops | Or : primop | And : primop inductive myexpr : Type | Primval : primval → myexpr | Primop : primop → myexpr | Let : var → myexpr → myexpr → myexpr | V : var → myexpr | F : fn → myexpr | If (pred : myexpr) : myexpr → myexpr → myexpr | Lambda (vs : list var) (body : myexpr) | App (op : myexpr) (args : list myexpr)
Unfortunately, MyList : list myexpr → primval doesn't have myexpr in scope -- I guess lean doesn't hoist definitions?
How would you go about doing this? Alternatively, is a resource on writing embedded DSLs in lean?
Oh, also, where is real?
Kenny Lau (Apr 14 2020 at 15:18):
```lean [code goes here] ```
Kenny Lau (Apr 14 2020 at 15:18):
import data.real.basic
Kenny Lau (Apr 14 2020 at 15:19):
you can have mutual inductive types
Reid Barton (Apr 14 2020 at 15:20):
In this case, though, the path of least resistance is most likely to "inline" primval into myexpr, given that you use it only once
Reid Barton (Apr 14 2020 at 15:20):
(A list of any-values-whatsoever isn't so "primitive" anyways, is it?)
Sam Stites (Apr 14 2020 at 15:21):
Thanks @Kenny Lau! Am I doing something wrong here, then? This example fails to compile.
@Reid Barton I can't inline as I'm aiming for something slightly more complex.
Reid Barton (Apr 14 2020 at 15:22):
Check the section of TPIL on mutual inductive types
Kevin Buzzard (Apr 14 2020 at 15:22):
https://leanprover.github.io/theorem_proving_in_lean/induction_and_recursion.html#mutual-recursion
Reid Barton (Apr 14 2020 at 15:23):
they have their own syntax, and they are handled by translation into an indexed inductive type so they are less pleasant to handle
Kevin Buzzard (Apr 14 2020 at 15:23):
Sam Stites (Apr 14 2020 at 15:23):
Perfect! Thank you!
Reid Barton (Apr 14 2020 at 15:24):
don't say you weren't warned :smiling_devil:
Jannis Limperg (Apr 14 2020 at 15:25):
Not long ago, I asked @Gabriel Ebner a question about some mutual inductives and his answer was something along the lines of, "don't ever use them, they don't work". In that spirit: Has anyone actually used mutual inductives successfully?
Reid Barton (Apr 14 2020 at 15:25):
(At a high level, the situation is: the Lean kernel does not support mutually inductive types; so either you have to use mutual or manage the translation yourself, each with its own downsides)
Reid Barton (Apr 14 2020 at 15:26):
Apparently there is exactly one use in mathlib, in a file I've never looked at
Reid Barton (Apr 14 2020 at 15:27):
oh but they are mutually inductive Props, which probably makes life simpler
Reid Barton (Apr 14 2020 at 15:32):
Maybe they are usable if you only care about programming and not proving?
Reid Barton (Apr 14 2020 at 15:33):
Heh, these search results are also representative I think: https://github.com/flypitch/flypitch/search?q=mutual&unscoped_q=mutual
Sam Stites (Apr 14 2020 at 15:33):
Ultimately, I'm hoping to formally verify some DSLs so hopefully these don't fall apart too fast
Reid Barton (Apr 14 2020 at 15:33):
One use in a subdirectory old/, and a comment in the real code explaining that they are not convenient
Scott Morrison (Apr 15 2020 at 01:45):
Every attempt I've made to use them has worked at first, and them quickly led to despair (usually the definitional equations not being usable for some reason), and me asking the experts here "how do I write this without mutual inductives"?
hakanaras (Oct 03 2025 at 12:09):
How can I achieve what I'm trying to do here:
mutual
structure HostField where
name : String
type : HostType
default? : Option (Default type)
structure HostProduct where
name : String
fields : List HostField
inductive HostType where
| string : HostType
| product : HostProduct -> HostType
inductive Default : HostType -> Type where
| string : String -> Default HostType.string
end
I know that it's not possible to use one mutual inductive type as a parameter for another. I just want to know how this could be achieved instead. ("This" being that I want to provide a default value of the type in HostField.type sometimes)
Also I know the difference between parameters and indices on inductive/structure types, but what is the difference between parameters and indices on constructors? Don't they behave the same for the most part?
Rob Simmons (Oct 03 2025 at 12:18):
Would it represent your intent to paramaterize the types by the type of their default value?
mutual
structure HostField' a where
name : String
type : HostType' a
default? : Option a
structure HostProduct' a where
name : String
fields : List (HostField' a)
inductive HostType' a where
| string : HostType' a
| product : HostProduct' a -> HostType' a
end
def HostField := HostField' String
def HostProduct := HostProduct' String
def HostType := HostType' String
hakanaras (Oct 03 2025 at 12:22):
Not really. In practice there are lots of different constructors of HostType. This was just a minimized example.
So one HostProduct can have several fields with different default value types.
Jason Reed (Oct 03 2025 at 20:43):
Just taking a guess here, but in the absence of induction-induction I would try independently defining untyped values in a separate mutual like
mutual
structure FieldVal : Type where
name : String
val : Val
inductive Val : Type where
| strVal : String → Val
| recVal : List FieldVal → Val
end
mutual
structure FieldTy where
name : String
type : Ty
default? : Option Val
inductive Ty where
| strTy : Ty
| recordTy : List FieldTy → Ty
end
and then defining what it means for a Val to be well-typed at a Ty, and a FieldVal at a FieldTy. But maybe there's some cleverer way to approximate what you want in Lean while still maintaining the intrinsic typing in your default? : Option (Default type). To me it smells very intrinsically induction-induction-ish though.
Jason Reed (Oct 03 2025 at 20:45):
Messages like this suggest to me "falling back to less-indexed representations then defining well-formedness predicates on them after the fact" (which is how I'd characterize my guess above) is in fact a standard way to cope with this in Lean.
hakanaras (Oct 04 2025 at 14:37):
I've tried a bunch of stuff, but at every turn I either run into a problem with non-strictly-positive types or constructors with local variables, or it ends up not being ergonomic to use.
I ended up having an inductive type HostVal and HostField.type has type HostType ⊕ HostVal. And each HostVal belongs to one HostType obviously.
Jakub Nowak (Oct 05 2025 at 23:32):
Should default value be part of the type though? That would make types with different default values not defeq. I would say default value is property of a definition of a type, or its constructors, not of type.
I vaguely recall having made similar design decision as you when writing interpreter for my language, and later having to refactor.
Actually, Lean has default value as part of type, so it's likely to be ok. Maybe my case was something different.
Last updated: Dec 20 2025 at 21:32 UTC