Zulip Chat Archive
Stream: new members
Topic: Forall exists in assumption of an inductive
Harry Goldstein (Jun 16 2024 at 16:30):
I'm trying to write a pretty complicated inductive definition for an evaluation relation of a programming language, and I think (unless I'm mistaken) that I can't really do it without some nested quantifiers in an argument:
import Batteries.Data.AssocList
open Batteries (AssocList)
inductive Value : Type :=
| tt
| ff
| tuple (v₁ v₂ : Value)
| nondet (vs : List Value)
deriving Repr, BEq, Inhabited
inductive Expr : Type :=
| tt
| ff
| tuple (e₁ e₂ : Expr)
| fst (e : Expr)
| snd (e : Expr)
| ifElse (e₁ e₂ e₃ : Expr)
| ret (e : Expr)
| var (x : String)
| bind (x : String) (e₁ e₂ : Expr)
| flip
deriving Repr, BEq, Inhabited
inductive Eval : AssocList String Value → Expr → Value → Prop
| tt : Eval σ .tt .tt
| ff : Eval σ .ff .ff
| flip : Eval σ .flip (.nondet [.tt, .ff])
| tuple :
Eval σ e₁ v₁ →
Eval σ e₂ v₂ →
Eval σ (.tuple e₁ e₂) (.tuple v₁ v₂)
| fst :
Eval σ e (.tuple v₁ v₂) →
Eval σ (.fst e) v₁
| snd :
Eval σ e (.tuple v₁ v₂) →
Eval σ (.snd e) v₂
| ifTrue :
Eval σ e₁ .tt →
Eval σ e₂ v →
Eval σ (.ifElse e₁ e₂ e₃) v
| ifFalse :
Eval σ e₁ .ff →
Eval σ e₃ v →
Eval σ (.ifElse e₁ e₂ e₃) v
| ret :
Eval σ e v →
Eval σ (.ret e) (.nondet [v])
| var :
σ.find? x = some v →
Eval σ (.var x) v
| bind :
Eval σ e₁ (.nondet vs₁) →
(∀ v ∈ vs, ∃ v₁ ∈ vs₁, Eval (σ.cons x v₁) e₂ (.nondet vs₂) ∧ v ∈ vs₂) →
Eval σ (.bind x e₁ e₂) (.nondet vs)
Lean complains about "invalid nested inductive datatype 'Exists', nested inductive datatypes parameters cannot contain local variables", and I think I see the logic for why, but I'm not sure if and how I can express something equivalent that Lean can handle.
Shanghe Chen (Jun 16 2024 at 16:33):
It seems the problem happened in the bind
constructor. If it's commented then no error is reported
Harry Goldstein (Jun 16 2024 at 16:36):
Yes, but I do need the bind
constructor...
Shanghe Chen (Jun 16 2024 at 16:37):
https://leanprover.zulipchat.com/#narrow/stream/270676-lean4/topic/.E2.9C.94.20Type.20Mismatch.3F a quite old thread seems containing a similar error.
Harry Goldstein (Jun 16 2024 at 16:42):
Yes, I think these issues do seem related. Although in this case I'm not sure I understand how to unravel the inductive in the same way. If there are other examples of this kind of thing in practice that would be super helpful
Shanghe Chen (Jun 19 2024 at 04:08):
Hi have you solved the problem? Or could you please share the background of the problem, or simplify the programming language but still with the problematic issue?
Harry Goldstein (Jun 19 2024 at 14:14):
No, unfortunately I haven't yet. The core problem, I think, is that I need to encode the ∀∃
in the bind
constructor as a separate mutually-inductive relation, but I'm not really sure how to do that. I encoded the relation as a function for now (it's really just bind
in the List
monad a couple of times) but someday I would like to figure out the right way to write this as an inductive.
Shanghe Chen (Jun 19 2024 at 14:25):
I am wondering if it’s #xy or not. Could you please share the original programming language, it’s kind of like untyped lambda calculus? And what’s the main result wanna to prove?
Harry Goldstein (Jun 19 2024 at 15:29):
It's a nondeterministic calculus, and I'm trying to prove that its semantics is equivalent to the semantics of the same program compiled to a lower-level language. But really the main important thing is that the bind
constructor evaluates its first argument, then for any v₁
that argument could evaluate to it evaluates its second argument with v₁
appended to the context, and finally it joins all of those lists together. This is what the interpretation looks like as a function:
def Expr.interp (e : Expr) (σ : AssocList String Value) : Option Value :=
match e with
...
| .bind x e₁ e₂ => do
let .nondet vs ← e₁.interp σ | none
let vss ← vs.mapM (λ v => do
let .nondet vs' ← e₂.interp (σ.cons x v) | none
pure vs')
pure (.nondet vss.join)
The inductive I posted tries to capture the same thing, just by constraining the values in the output list, since I don't think it makes sense to map a relation over a list.
Does that help? I don't think I'm too far off base with what I'm trying to do, I just don't know the right way to do it.
Shanghe Chen (Jun 20 2024 at 15:17):
I dont know if I understand the original problem correctly or not. Does using more inductive construction and avoiding Exist
and Forall
in the definition of Eval
help? Maybe the definition of it should be totally inductively built up. I did an exercise for the nondeterministic addtion of two list too. It's kind of tautological to me:
abbrev Var := String
abbrev Values := List Nat
/--
all expressions have forms like:
- value, like [1,2,3]
- variable, like "x"
- operations, here simply an addition
- a bind epression, which means that
the expression e1 will be evaluated,
and bind to the variable x
-/
inductive Expr : Type
| val (vs : Values)
| var (x : Var)
| add (e1 e2: Expr)
| bind (x: Var) (e1 e2 : Expr)
/--
The context gives each variable a result containing nondeterministic values
-/
abbrev Context := Var -> Values
/-- the auxiliary definition for nondeterministic addition -/
def add : Values -> Values -> Values
| [], _ => []
| (x::xs), ys => List.map (x + ·) ys ++ add xs ys
/--
update the context for variable x
-/
def bind (c: Context) (x: Var) (vs : Values) : Context :=
fun y => if y = x then vs else c y
/--
inductively define the evaluation proposition, a term of
`derive ctx expr vs` means that under the context `ctx`, the `expr`
should be evaluated to `vs`
Here the inductive families should represent the sematic for the real
evaluation, i.e, a prop in the families has term(or, terms, for Prop has only one term)
iff it does evaluated to the values
-/
inductive derive : Context -> Expr -> Value -> Prop
| val c vs: derive c (.val vs) vs
| var c x: derive c (.var x) (c x)
| add c e1 e2 vs1 vs2:
derive c e1 vs1 -> derive c e2 vs2
-> derive c (.add e1 e2) (add vs1 vs2)
/-- for `.bind x e1 e2`, only `e1` evaluated to some `vs1` and after
binding it to `x`, `e2` evalued to some `vs2`
-/
| bind c e1 e2 vs1 vs2 x:
derive c e1 vs1 -> derive (bind c x vs1) e2 vs2
-> derive c (.bind x e1 e2) vs2
def c0 : Context
| _ => []
abbrev x:= "x"
abbrev y:= "y"
example : derive c0 (.bind x (.val [1,2,3]) (.bind y (.val [4,5,6]) (.add (.var x) (.var y)))) [5,6,7,6,7,8,7,8,9] := by
apply derive.bind
apply derive.val
apply derive.bind
apply derive.val
have eq: add [1,2,3] [4,5,6] = [5,6,7,6,7,8,7,8,9] := by rfl
rw [←eq]
apply derive.add
apply derive.var
apply derive.var
/--
real evaluatation
-/
def eval (c: Context) : Expr -> Values
| .val vs => vs
| .var x => c x
| .add x y => add (eval c x) (eval c y)
| .bind x e1 e2 => eval (bind c x (eval c e1)) e2
def comp (c: Context) (vs: Values) : (e: Expr) -> derive c e vs ↔ eval c e = vs
| .val vs => by
constructor
. intro ev
cases ev
simp [eval]
. intro ev
rw [←ev]
apply derive.val
| .var x => by
constructor
. intro ev
cases ev
simp [eval]
. intro ev
rw [←ev]
apply derive.var
| .add x y => by
constructor
. intro ev
cases ev
simp [eval]
rw [(comp c _ x).mp (by assumption)]
rw [(comp c _ y).mp (by assumption)]
. intro ev
cases ev
apply derive.add
apply (comp ..).mpr
rfl
apply (comp ..).mpr
rfl
| .bind x e1 e2 => by
constructor
. intro ev
cases ev
simp [eval]
rw [(comp c _ e1).mp (by assumption)]
rw [(comp _ _ e2).mp (by assumption)]
. intro ev
cases ev
apply derive.bind
apply (comp c (eval c e1) _).mpr
rfl
apply (comp ..).mpr
rfl
Harry Goldstein (Jun 20 2024 at 17:04):
The problem is that the semantics of the language isn't 100% nondeterministic. There are deterministic values like true
and nondeterministic ones specified by nondet
. I'm mimicking the behavior you'd get with the Gen
monad in a library like QuickCheck (I'm not sure if Lean has an equivalent). Anyway, I really don't think it's worth your time to try to understand why the semantics I wrote is the one I want---I just want to know if Lean can represent it.
Last updated: May 02 2025 at 03:31 UTC