Zulip Chat Archive

Stream: metaprogramming / tactics

Topic: unify


Fabian Glöckle (May 10 2022 at 15:52):

Hi, I seem to be unable to find this in the meta API:
Given an expr e and an expr M potentially containing metavariables, what is the library function which succeeds iff e and M unify under a suitable metavariable assignment? It does not seem to be tactic.unify, which fails e.g. on list nat and list A, A being a metavariable of type Type.
Or is this not what metavariables are made for / the elaborator is accessed differently / ... ?
(I am still an absolute metaprogramming newbie)

Damiano Testa (May 10 2022 at 15:58):

I'm also a newbie, so I do not really know an answer to your question. However, the code below did what I wanted and "unified metavariables":

open tactic

/-- A `tactic bool` computing whether the user-input `e` unifies with `e'`. -/
meta def expr.unify_with (e e' : expr) : tactic bool :=
succeeds $ unify e e'

Damiano Testa (May 10 2022 at 15:59):

This allowed me to pass _ * _ for instance and it unified with 3 * 4 and filled in the _ with a term of type nat.

Damiano Testa (May 10 2022 at 15:59):

However, in my use-case, the metavariables were explicit _, not some expr whose type I did not know. If this even makes sense...

Fabian Glöckle (May 10 2022 at 16:01):

Is _ the same thing as an expr.mvar?

Henrik Böving (May 10 2022 at 16:02):

_ will put a meta variable at the spot it's used yes.

Fabian Glöckle (May 10 2022 at 16:04):

meta def mk_mvar' (pp_name : name) (type : expr) : tactic expr := do
uniq_name  mk_fresh_name,
return $ mvar uniq_name pp_name type

meta def mwe : tactic unit :=
do
  m  mk_mvar' `M `(Type),
  n  to_expr ``(_),
  trace (to_raw_fmt n),
  trace (to_raw_fmt m),
  unify n `(nat),
  unify m `(nat)

Fabian Glöckle (May 10 2022 at 16:05):

With output

(mvar _mlocal._fresh.2495.965 _mlocal._fresh.2495.965 (const 2 []))
(mvar _fresh.2495.962 M (sort 1))

and error

unify tactic failed, failed to unify
  M : Type
and
   : Type

Fabian Glöckle (May 10 2022 at 16:08):

Why is the type of the _ metavariable (const 2 [])?

Damiano Testa (May 10 2022 at 16:10):

If I understand correctly, the first unify succeeds: i.e. this works:

meta def mwe : tactic unit :=
do
  m  mk_mvar' `M `(Type),
  n  to_expr ``(_) tt ff,
  trace (to_raw_fmt n),
  trace (to_raw_fmt m),
  unify n `(nat)

Fabian Glöckle (May 10 2022 at 16:11):

Yes, the _ metavariable unifies, but my handcrafted one doesn't
The difference is the type of the expr.mvar
But I do not see why it should be (const 2 [])... ?!

Fabian Glöckle (May 10 2022 at 16:11):

@Jannis Limperg maybe? :)

Damiano Testa (May 10 2022 at 16:12):

Sorry, I am not sure that I can be of further/any help! I think that the _ is important and should be literally present, but I do not know. :shrug:

Fabian Glöckle (May 10 2022 at 16:13):

Damiano Testa said:

Sorry, I am not sure that I can be of further/any help! I think that the _ is important and should be literally present, but I do not know. :shrug:

Yes, thank you a lot for pointing toward the underscore. That will work and I hope the pros will tell me what is behind the mysterious 2 ;)

Jannis Limperg (May 10 2022 at 16:18):

Exprs representing mvars and hypotheses often contain nonsensical types like these. infer_type will give you the right type. :shrug:

Fabian Glöckle (May 10 2022 at 16:18):

"/- [WARNING] Do not trust the types for mvar and local_const,
they are sometimes dummy values. Use tactic.infer_type instead. -/" I see

Jannis Limperg (May 10 2022 at 16:19):

Yep yep. (Fixed in Lean 4 thankfully.)

Fabian Glöckle (May 10 2022 at 16:19):

So how would I write this instead?

meta def mk_mvar_pis : expr  tactic (list expr × expr)
| (pi n _ d b) := do
  p  mk_mvar' n d,
  (ps, r)  mk_mvar_pis (instantiate_var b p),
  return ((p :: ps), r)
| e := return ([], e)

Fabian Glöckle (May 10 2022 at 16:19):

(taken from mk_local_pis)

Fabian Glöckle (May 10 2022 at 16:20):

I would like to retrieve the body of a (nested) pi, but with something unifiable instead of var 0 etc inside

Jannis Limperg (May 10 2022 at 16:23):

I wrote a comprehensive library for this sort of task a while ago. docs#tactic.open_pis_metas should be what you're looking for.

Fabian Glöckle (May 10 2022 at 16:26):

wow great, exactly what I need!

Fabian Glöckle (May 10 2022 at 16:27):

I didn't find it because I was looking for mvars, not metas...

Fabian Glöckle (May 10 2022 at 16:28):

and I am learning from your code that one should not create mvars by the expr.mvar constructor but by mk_meta_var, right?

Jannis Limperg (May 10 2022 at 16:31):

If I used it there, probably. ;) I don't recall the details.

Jannis Limperg (May 10 2022 at 16:33):

Ah yeah if you use the expr constructor directly, you create a malformed expression. Metavariables which are referred to in an expression must be registered in the environment and mk_meta_var does exactly that.

Henrik Böving (May 10 2022 at 16:53):

Just as a learning experience for myself^^ @Jannis Limperg

i put all of my (admittedly rather little) knowledge of the Lean 4 Meta API into this:

def foo : MetaM Unit := do
  let u  Meta.mkFreshLevelMVar
  let a  Meta.mkFreshExprMVar (mkSort u)
  let listA := mkApp (mkConst `List [u]) a
  let listNat  Meta.mkAppM `List #[mkConst `Nat]
  IO.println s!"{listA}"
  IO.println s!"{listNat}"
  if Meta.isDefEq listA listNat then
    IO.println "wooo"
    let newlistA  Meta.instantiateMVars listA
    IO.println s!"{newlistA}"
  else
    let newlistA  Meta.instantiateMVars listA
    IO.println s!"{newlistA}"
    IO.println "booo"

#eval foo

but I get a boo and no meta variables are instantiated, why's that the case? The types it prints are

List.{?_uniq.1} ?_uniq.2
List.{0} Nat

those look to me like they should be solvable no?

Jannis Limperg (May 10 2022 at 18:45):

Took me a bit to figure out as well. It's a level issue: mkApp (mkConst `List [u]) a is not type-correct. List.{u} has type Type u -> Type u, but a has type Sort u. Complete fix:

import Lean

open Lean
open Lean.Meta

def foo : MetaM Unit := do
  let u  mkFreshLevelMVar
  let a  mkFreshExprMVar (mkSort (mkLevelSucc u))
  let listA := mkApp (mkConst ``List [levelZero]) a
  let listNat  Meta.mkAppM ``List #[mkConst ``Nat]
  IO.println s!"{listA}"
  IO.println s!"{listNat}"
  if  Meta.isDefEq listA listNat then
    IO.println "wooo"
    let newlistA  Meta.instantiateMVars listA
    IO.println s!"{newlistA}"
  else
    let newlistA  Meta.instantiateMVars listA
    IO.println s!"{newlistA}"
    IO.println "booo"

#eval foo

Other comments which are hopefully helpful:

  • Use ``List instead of `List where possible.
  • I usually use trace[debug] for printf-style debugging.

Last updated: Dec 20 2023 at 11:08 UTC