Zulip Chat Archive
Stream: lean4
Topic: argument has type _nested
Anatoliy Baskakov (Aug 08 2024 at 17:26):
I was trying to implement lambda calculus extended with records with unique keys.
The code goes
namespace List
def keys (pairs : List (α × β)) : List α
:= match pairs with
| [] => []
| ⟨a, _⟩ :: rest => a :: keys rest
def values (pairs : List (α × β)) : List β
:= match pairs with
| [] => []
| ⟨_, b⟩ :: rest => b :: values rest
end List
inductive Term : Type where
| var : String → Term
| app : Term → Term → Term
| lam : String → Term → Term
| record : (entries : List (String × Term)) → (unique : entries.keys.Nodup) → Term
It results in an error:
application type mismatch
List.keys entries
argument has type
_nested.List_1
but function has type
List (String × Term) → List String
I have tried to tweak the definition a bit, factor out the record into a separate record, define it both generically and mutually with the term itself.
I still do not fully understand the reasoning behind it. Does it have to do with inductive-inductive? Why are such types prohibited? Are there plans to support it in the future? Is there no way around it?
Jannis Limperg (Aug 08 2024 at 17:35):
You're defining a nested inductive type because Term
appears as an argument to another inductive type, List
(and Prod
). Lean internally translates such definitions into mutual inductive types. This translation creates a specialised List
type; this is the _nested.List_1
from the error message. But apparently it doesn't also create a specialised List.keys
, so entries.keys
becomes ill-typed. I would consider this a bug.
As a workaround, you could remove unique
from Term.record
and create a predicate Term.WellFormed : Term -> Prop
that characterises the well-formed terms (namely those where the record fields are unique). You'll probably want a type system anyway, in which case you can also push this property into the typing relation.
Edward van de Meent (Aug 08 2024 at 17:37):
i think the best way around this is not to use general List (A x B)
for this inductive, but rather a special-purpose inductive Record
definition, which you put together with Term
in a mutual
block.
Jannis Limperg (Aug 08 2024 at 17:42):
Even then, I think you'd have to define the function keys
on Record
mutually with Term
and Record
, so the type would become inductive-recursive (which is not supported). Is this actually a fundamental limitation of this translation?
Edward van de Meent (Aug 08 2024 at 17:43):
right....
Edward van de Meent (Aug 08 2024 at 17:43):
perhaps the best way is to rather use a get : String -> Option Term
parameter...
Edward van de Meent (Aug 08 2024 at 17:44):
wait no, that allows for infinite records...
Arthur Adjedj (Aug 08 2024 at 17:54):
One way to avoid this issue would be to parametrise your inductive by the function you're using, namely List.keys
. One caveat is that this inductive will live in a bigger universe:
inductive PreTerm (f : {α β : Type} → List (α × β) → List α): Type 1 where
| var : String → PreTerm f
| app : PreTerm f → PreTerm f → PreTerm f
| lam : String → PreTerm f → PreTerm f
| record : (entries : List (String × Term)) → (unique : (f entries).Nodup) → PreTerm f
def Term := PreTerm List.keys
Jannis Limperg (Aug 08 2024 at 18:06):
Nice idea, but you forgot to change Term
to PreTerm
in entries
. And with that change, it doesn't work any more because of the universe bump:
inductive PreTerm (f : {α β : Type} → List (α × β) → List α): Type 1 where
| var : String → PreTerm f
| app : PreTerm f → PreTerm f → PreTerm f
| lam : String → PreTerm f → PreTerm f
| record : (entries : List (String × PreTerm f)) → (unique : (f entries).Nodup) → PreTerm f
/-
application type mismatch
f entries
argument
entries
has type
List (String × PreTerm fun {α β} => f) : Type 1
but is expected to have type
List (?m.703 entries × ?m.704 entries) : Type
-/
Arthur Adjedj (Aug 08 2024 at 19:16):
Oups.
Mario Carneiro (Aug 08 2024 at 22:56):
Here's a definition that works:
inductive PreTerm : Type where
| var : String → PreTerm
| app : PreTerm → PreTerm → PreTerm
| lam : String → PreTerm → PreTerm
| record : (entries : List (String × PreTerm)) → PreTerm
mutual
def PreTerm.WF : PreTerm → Prop
| .var _ => True
| .app f a => f.WF ∧ a.WF
| .lam _ e => e.WF
| .record e => e.keys.Nodup ∧ WF_list e
def PreTerm.WF_list : List (String × PreTerm) → Prop
| [] => True
| (_, e) :: es => e.WF ∧ WF_list es
end
def Term := {e : PreTerm // e.WF}
Edward van de Meent (Aug 09 2024 at 09:26):
indeed. although you might want to name it wellFormed
rather than WF
to distinguish it from wellFounded
Anatoliy Baskakov (Aug 09 2024 at 11:22):
Thank you for your answers! I will see what I can do with it.
Additionally, I have opened an issue on Lean's github, hopefully they will address it to simplify the matter
Last updated: May 02 2025 at 03:31 UTC