Zulip Chat Archive

Stream: lean4

Topic: Unification issue for indices


Jakob von Raumer (Feb 14 2025 at 13:36):

So this is not working but we really would like it to (it appears in an automatically generated file, that's why there's an unreduced index in the binder):

example : BitVec (4 * 4) :=
  BitVec.append (BitVec.zero 2) (BitVec.zero 14)

example (α : Type) (a : α) : Vector α (4 * 4) :=
  Vector.append (Vector.mkVector 2 a) (Vector.mkVector 14 a)

This really should work, shouldn't it? :thinking:

Jakob von Raumer (Feb 14 2025 at 13:38):

(What happens is probably that 4 * 4 is unfolded to 4 * 3 + 4 once and that decomposition is unified with the index of the return type of append)

Jakob von Raumer (Feb 14 2025 at 13:55):

Having an explicit coercion works, but is that the best workaround?

Joachim Breitner (Feb 14 2025 at 14:19):

Strange indeed. And (JFTR, not surprising) not specific to BitVec:

-- set_option trace.Meta.isDefEq true

axiom BV : Nat  Type
axiom BV.zero n : BV n
axiom BV.append {n m} : BV n  BV m  BV (n + m)

/--
error: application type mismatch
  (BV.zero 2).append
argument
  BV.zero 2
has type
  BV 2 : Type
but is expected to have type
  BV (Nat.mul 4 3) : Type
-/
#guard_msgs in
noncomputable example : BV (4 * 4) :=
  BV.append (BV.zero 2) (BV.zero 14)

#guard_msgs in
noncomputable example : BV (4 * 4) :=
  BV.append (BV.zero 12) (BV.zero 4)

Joachim Breitner (Feb 14 2025 at 14:20):

You don't need a coercion, even a type ascription helps:

noncomputable example : BV (4 * 4) :=
  (BV.append (BV.zero 2) (BV.zero 14) : BV 16)

Joachim Breitner (Feb 14 2025 at 14:24):

It is also not Nat-specific (which I would not have been surprised about):

-- set_option trace.Meta.isDefEq true

inductive MyNat where | zero | succ : MyNat  MyNat
def MyNat.add : MyNat  MyNat  MyNat
  | a, .zero   => a
  | a, .succ b => .succ (.add a b)
def MyNat.mul : MyNat  MyNat  MyNat
  | _, .zero   => .zero
  | a, .succ b => .add (.mul a b) a

def two := MyNat.zero.succ.succ.succ.succ
def four := MyNat.zero.succ.succ.succ.succ
def fourteen := MyNat.zero.succ.succ.succ.succ.succ.succ.succ.succ.succ.succ.succ.succ.succ.succ

axiom BV : MyNat  Type
axiom BV.zero n : BV n
axiom BV.append {n m} : BV n  BV m  BV (.add n m)

/--
error: application type mismatch
  (BV.zero two).append
argument
  BV.zero two
has type
  BV two : Type
but is expected to have type
  BV (four.mul MyNat.zero.succ.succ.succ) : Type
-/
#guard_msgs in
noncomputable example : BV (.mul four four) :=
  BV.append (BV.zero two) (BV.zero fourteen)

Jakob von Raumer (Feb 14 2025 at 14:26):

Yes, I think it's a generic effect of the unification order

Eric Wieser (Feb 14 2025 at 14:27):

No coercion required:

example : BitVec (4 * 4) :=
  (BitVec.append (BitVec.zero 2) (BitVec.zero 14) :)

Jakob von Raumer (Feb 14 2025 at 14:30):

What's :), just a vacuous type annotation?

Joachim Breitner (Feb 14 2025 at 14:31):

Jakob von Raumer said:

Yes, I think it's a generic effect of the unification order

It seems to be assigning metavariables during “lazy“ unification:

Meta.isDefEq] ✅️ BV (four.mul four) =?= BV (MyNat.add ?m.778 ?m.779) 
  [] ✅️ four.mul four =?= MyNat.add ?m.778 ?m.779 
    [] ✅️ (four.mul MyNat.zero.succ.succ.succ).add four =?= MyNat.add ?m.778 ?m.779 
      [] ✅️ four.mul MyNat.zero.succ.succ.succ =?= ?m.778 
        [] four.mul MyNat.zero.succ.succ.succ [nonassignable] =?= ?m.778 [assignable]
        [] ✅️ MyNat =?= MyNat
      [] ✅️ four =?= ?m.779 
        [] four [nonassignable] =?= ?m.779 [assignable]
        [] ✅️ MyNat =?= MyNat

Kyle Miller (Feb 14 2025 at 17:19):

@Jakob von Raumer (e :) elaborates e completely (synthesizing instances and all that), and only then does it take the expected type into account, unifying the type of e with the expected type and otherwise inserting a coercion. On the other hand, (e : t) elaborates e using t as the expected type, inserting a coercion if the types don't line up.

Tomas Skrivan (Feb 14 2025 at 19:09):

By changing the signature of Vector.append you can also get the reduction (2+14 ==> 16) in the type automatically

import Lean

open Lean Std

def Vector.append' (x : Vector α n) (y : Vector α m) {mn} (hmn : mn = n + m := by (conv => rhs; simp); try rfl) : Vector α mn :=
  hmn  x.append y

example (α : Type) (a : α) : Vector α (4 * 4) :=
  Vector.append' (Vector.mkVector 2 a) (Vector.mkVector 14 a)

Also

#check Vector.append' (Vector.mkVector 2 a) (Vector.mkVector 14 a)

is of type Vector α 16 and not Vector α (2+14)

Jakob von Raumer (Feb 15 2025 at 11:44):

@Tomas Skrivan I've employed this trick before, but it always feels a bit dirty, especially if you'd have to wrap multiple of such functions.
dsimp only is probably enough, right?

Kim Morrison (Feb 15 2025 at 21:49):

This couldn't replace the definition of Vector.append, but it is tempting to add it as a variant.

Jakob von Raumer (Feb 16 2025 at 12:39):

I understand, but it's unclear for how many operation we'd have to add such a wrapper. BitVec.replicate seems prone to the same effect, with pow and mul instead of mul and add, for example...

Jakob von Raumer (Feb 17 2025 at 08:48):

Jakob von Raumer said:

I understand, but it's unclear for how many operation we'd have to add such a wrapper. BitVec.replicate seems prone to the same effect, with pow and mul instead of mul and add, for example...

Hmm, I can't actually expose the same thing with BitVec.replicate, that's weird

Leo Stefanesco (Feb 21 2025 at 09:54):

We have found another strange unification failure:

def Sail.BitVec.updateSubrange
  {w : Nat} (x : BitVec w) (hi lo : Nat) (y : BitVec (hi - lo + 1))
  : BitVec w := by sorry

def _update_cr_type_bits (v : (BitVec 8)) (x : (BitVec 8)) : (BitVec 8) :=
  (Sail.BitVec.updateSubrange v (8 - 1) 0 x)

fails with

application type mismatch
  Sail.BitVec.updateSubrange v (8 - 1) 0 x
argument
  x
has type
  BitVec 8 : Type
but is expected to have type
  BitVec (8 - 1 - 0 + 1) : Type

Apparently this used to works with lean 4.14

James Gallicchio (Feb 21 2025 at 22:16):

further minimized:

opaque MyTy (n : Nat) : Type
opaque foo (hi) (_y : MyTy (hi + 1)) : Unit

set_option trace.Meta.isDefEq true

def bar (x : MyTy 8) := foo (8-1)
                          x   -- type error
def bar' (x : MyTy 8) := foo (HSub.hSub 8 1)
                          x   -- succeeds

It is surprising to me that Nat.sub behaves different from the notation. The isDefEq traces encounter the same goal in both, but bar' continues where bar' fails, so I assume this is some defeq heuristic messing up?

Yakov Pechersky (Feb 21 2025 at 22:34):

Is there an elaboration issue of inferring the type of 8-1 that you sidestep by the explicit Nat in bar'?

Jakob von Raumer (Feb 21 2025 at 22:42):

Yakov Pechersky said:

Is there an elaboration issue of inferring the type of 8-1 that you sidestep by the explicit Nat in bar'?

I don't think that's the issue, the trace looks like it seems to figure out that it's Nat before the failure. For some reason when looking at (Nat.sub 8 0).pred it matches on Nat.sub 8 0 without ever reducing it, that seems to be part of the issue


Last updated: May 02 2025 at 03:31 UTC