Zulip Chat Archive
Stream: new members
Topic: Trying to understand exprs
Joe Palermo (Apr 27 2021 at 15:02):
Hi,
I'm trying to develop a deeper understanding of expr. Would it be fair to say that the following expressions are equivalent?
#check λ {α : Sort 1}, α
#check expr.lam (mk_simple_name "α") binder_info.implicit (expr.sort $ level.succ level.zero) (expr.var 0)
I'm surprised to find that they don't type check to the same thing.
Jason Rute (Apr 27 2021 at 16:05):
An expression is a meta object in the sense that it is how Lean internally represents things. So the second is the internal representation of the first. They are related but not the same. For example, the first one has type Type -> Type
(or at least a type equal to that) and the second has type expr tt
(which is the same as expr
since the tt
is an optional parameter which defaults to true). Note, you can change the first to be an expr
with the backquote trick: See here:
This has type Type -> Type
#check λ {α : Sort 1}, α
#check `(λ {α : Sort 1}, α) # this has type `reflected (λ {α : Type}, α)` which can be lifted to `expr`
#check (`(λ {α : Sort 1}, α) : expr) # Here we lift it to expr
#eval (`(λ {α : Sort 1}, α) : expr).to_raw_fmt.to_string # we can see this is the desired expression
Alex J. Best (Apr 27 2021 at 16:05):
Yes I would say the expr on the second line represents the way you've defined a term on the first. But the second is only a syntactic representation so the two lines don't have the same type, as the first is some thing in the type theory, whereas the second is the internal way lean builds these terms.
Indeed if you reflect the first line to an expr using `(term)
, you can check that they are equal:
#eval to_bool ((`(λ {α : Sort 1}, α) : expr) = expr.lam (mk_simple_name "α") binder_info.implicit (expr.sort $ level.succ level.zero) (expr.var 0))
Joe Palermo (Apr 27 2021 at 18:55):
Thanks that helps @Jason Rute @Alex J. Best
Joe Palermo (Apr 27 2021 at 19:10):
So I see now that you can take a term and convert it into an expr by reflecting and lifting it. Can you reverse this process, and turn it back into a term?
Mario Carneiro (Apr 27 2021 at 19:12):
reflect
will do that for some types
Mario Carneiro (Apr 27 2021 at 19:13):
er, it depends on what you mean by "term"
Mario Carneiro (Apr 27 2021 at 19:13):
for me "term" = expr
Mario Carneiro (Apr 27 2021 at 19:20):
I guess you mean the other direction, which means evaluating an expression into a given type. This is also only possible for some types but can be done using eval_expr
open tactic
set_option pp.numerals false
#eval trace (`(23:ℕ):expr) -- bit1 (bit1 (bit1 (bit0 has_one.one)))
#eval trace (reflect 23) -- bit1 (bit1 (bit1 (bit0 has_one.one)))
#eval do
n : ℕ ← eval_expr ℕ (`(23:ℕ):expr),
guard (n = 23)
Joe Palermo (Apr 27 2021 at 20:03):
Perhaps what I'm asking doesn't even make sense, but what I'm really asking is if one can take
(`(λ {α : Sort 1}, α) : expr)
and reverse the reflection + lifting to get back:
λ {α : Sort 1}, α
Joe Palermo (Apr 27 2021 at 20:09):
(deleted)
Jason Rute (Apr 27 2021 at 21:37):
In this case, I don't think so. First, you would have to know the type, which is (Type -> Type)
and but I don't think eval_expr
works with abstract types like that. Here is code which doesn't work:
open tactic
#eval do
f : (Type -> Type) ← eval_expr (Type -> Type) (`(λ {α : Sort 1}, α) : expr), -- doesn't work
return ()
The problem is that Type -> Type
is not reflectable. Without getting into the details, these are the sort of types that you can serialize into concrete data, like nat
, or list bool
. An abstract type like Type -> Type
isn't going to work.
Jason Rute (Apr 27 2021 at 21:45):
Having said that, you are barking up the wrong tree. If I can read past your questions, I think you want to take an expression which is stored in a file and check that it is a valid proof. If that is correct, you can do that with infer_type
:
#eval do
let e := (`(λ {α : Sort 1}, α) : expr),
tp <- tactic.infer_type e, -- this is valid
tactic.trace tp
#eval do
let e := (expr.lam (mk_simple_name "α") binder_info.implicit (expr.sort $ level.succ level.zero) (@expr.var tt 0)),
tp <- tactic.infer_type e, -- this is valid
tactic.trace tp
#eval do
let e := (expr.lam (mk_simple_name "α") binder_info.implicit (expr.sort $ level.succ level.zero) (@expr.var tt 1)),
tp <- tactic.infer_type e, -- this is not valid, so it fails at this line
tactic.trace tp
Jason Rute (Apr 27 2021 at 21:46):
I don't know how #check
works exactly, but I'm pretty sure it uses infer_type
or similar underneath.
Jason Rute (Apr 27 2021 at 21:49):
For your sort of projects, it is fine to work with expressions, instead of the objects they encode.
Joe Palermo (Apr 27 2021 at 22:02):
Thanks @Jason Rute. I was asking more out of pure curiosity than anything else, but yes that's right.
Jason Rute (Apr 27 2021 at 22:17):
Sorry, I likely jumped to the wrong conclusion then.
Joe Palermo (Apr 28 2021 at 15:02):
@Jason Rute No worries, it made me think and these examples are very helpful to see. Much appreciated.
Joe Palermo (Apr 28 2021 at 15:22):
Actually I'm wondering how make this one typecheck:
#eval do
let e := (expr.lam (mk_simple_name "b") binder_info.implicit (expr.const bool [])
(expr.app
(expr.app
(expr.app (expr.const eq.mp [level.zero])
(expr.app (expr.const not []) (expr.app (expr.app (expr.app (expr.const eq [level.succ level.zero]) (expr.const bool [])) (@expr.var tt 0)) (expr.const bool.tt []))))
(expr.app (expr.app (expr.app (expr.const eq [1]) (expr.const bool [])) (@expr.var tt 0)) (expr.const bool.ff [])))
(expr.app (expr.const eq_ff_eq_not_eq_tt []) (@expr.var tt 0)))),
tp <- tactic.infer_type e, -- this is valid
tactic.trace tp
Lean is complaining about the use of expr.const for some reason. Btw this proof corresponds to:
λ {b : bool}, @eq.mp.{0} (not (@eq.{1} bool b bool.tt)) (@eq.{1} bool b bool.ff) (eq_ff_eq_not_eq_tt b)
I simply printed this proof in raw_fmt, added expr prefixes and copied a few other things from the previous example (like using "@expr.var tt 0" instead of "expr.var 0") to try and get it to type check.
Alex J. Best (Apr 28 2021 at 22:35):
docs#expr.const needs a name as input whereas you are providing the literal term, you just have to prefix everything with a backtick to make this work though
import tactic
#eval do
let e := (expr.lam (mk_simple_name "b") binder_info.implicit (expr.const `bool [])
(expr.app
(expr.app
(expr.app (expr.const `eq.mp [level.zero])
(expr.app (expr.const `not []) (expr.app (expr.app (expr.app (expr.const `eq [level.succ level.zero]) (expr.const `bool [])) (@expr.var tt 0)) (expr.const `bool.tt []))))
(expr.app (expr.app (expr.app (expr.const `eq [level.succ level.zero]) (expr.const `bool [])) (@expr.var tt 0)) (expr.const `bool.ff [])))
(expr.app (expr.const `eq_ff_eq_not_eq_tt []) (@expr.var tt 0)))),
tp <- tactic.infer_type e, -- this is valid
tactic.trace tp
Last updated: Dec 20 2023 at 11:08 UTC