Zulip Chat Archive

Stream: lean4

Topic: Local variables of function behavior differs vs arguments


Pavel Klimov (Sep 24 2025 at 09:37):

Here is some types

inductive Ty
| int
| str
deriving DecidableEq

def Ty.toType : Ty  Type
| .int => Int
| .str => String

structure TypedVal where
type : Ty
value : type.toType

(edit: changed "implicit" -> "explicit")
Following code doesn't work, it require explicit cast in many places with proofs

def TypedValue.eq (val1' val2' : TypedVal) : Bool :=
  if h : val1'.type = val2'.type then
    let type := val1'.type
    let val1 : type.toType := val1'.value
    let val2 : type.toType := cast (by rw [ h]) val2'.value
    match type with
    | .int => BEq.beq (α := Int) val1 val2
    | .str => BEq.beq (α := String) val1 val2
  else
    false

While following code works without proofs

def TypedValue.eqHelper (type : Ty) (val1 val2 : type.toType) : Bool :=
  match type with
  | .int => BEq.beq (α := Int) val1 val2
  | .str => BEq.beq (α := String) val1 val2

So, my current solution is using second function inside previous, like this:

def TypedValue.eq1 (val1 val2 : TypedVal) : Bool :=
  if h : val1.type = val2.type then
    TypedValue.eqHelper val1.type val1.value (cast (by rw [h]) val2.value)
  else
    false

But, I would like to inline it inside function, because I'm not planning to use this helper for anything else.

So, I wonder why inlining doesn't work while all types still the same? :thinking:

Henrik Böving (Sep 24 2025 at 09:52):

I don't see what you are getting at, both of your functions require the exact same amount of casts (namely 1)?

Pavel Klimov (Sep 24 2025 at 15:02):

@Henrik Böving have you tried to check my code? The first version is not working, and I have to add 4 explicit casts to make it work (some of them require proofs). (edit: changed "implicit" -> "explicit")

Pavel Klimov (Sep 24 2025 at 15:17):

Here is working version:

def TypedValue.eq (val1' val2' : TypedVal) : Bool :=
if h : val1'.type = val2'.type then
  let type := val1'.type
  let val1 : type.toType := val1'.value
  let val2 : type.toType := cast (by rw [ h]) val2'.value
  match h1 : type with
  | .int => BEq.beq (α := Int) (cast (by rw [h1]; rfl) val1) (cast (by rw [h1]; rfl) val2)
  | .str => BEq.beq (α := String) (cast (by rw [h1]; rfl) val1) (cast (by rw [h1]; rfl) val2)
else
  false

Henrik Böving (Sep 24 2025 at 18:25):

If I was doing it in a single function I'd probably do:

def TypedValue.eq (val1 val2 : TypedVal) : Bool :=
  if _h : val1.type = val2.type then
    match val1, val2 with
    | ⟨.int, val1, ⟨.int, val2 => BEq.beq (α := Int) val1 val2
    | ⟨.str, val1, ⟨.str, val2 => BEq.beq (α := String) val1 val2
  else
    false

Henrik Böving (Sep 24 2025 at 18:26):

Or I guess even

def TypedValue.eq (val1 val2 : TypedVal) : Bool :=
    match val1, val2 with
    | ⟨.int, val1, ⟨.int, val2 => BEq.beq (α := Int) val1 val2
    | ⟨.str, val1, ⟨.str, val2 => BEq.beq (α := String) val1 val2
    | _, _ => false

Alfredo Moreira-Rosa (Sep 24 2025 at 21:07):

Or even :grinning_face_with_smiling_eyes: :

def TypedValue.eq: TypedVal  TypedVal  Bool
    | ⟨.int, val1, ⟨.int, val2 => BEq.beq (α := Int) val1 val2
    | ⟨.str, val1, ⟨.str, val2 => BEq.beq (α := String) val1 val2
    | _, _ => false

Pavel Klimov (Sep 24 2025 at 21:10):

It's flaw of my example. Tag of type can be not so simple (without arguments), and comparing each argument of tag is cumbersome. So I want to avoid matching of both arguments because I would have to match arguments of constructors as well. (Constructors of inductive value)

Pavel Klimov (Sep 24 2025 at 21:11):

Also, it does not answer why arguments work differently in comparison with arguments of function.

Alfredo Moreira-Rosa (Sep 24 2025 at 21:24):

if you don't want to match constructor arguments, just use _ ?

Pavel Klimov (Sep 24 2025 at 21:29):

I have to match them, because it's tag of type. toType depends on them.

Alfredo Moreira-Rosa (Sep 24 2025 at 21:39):

you can extract before the match :

def TypedValue.eq (val1' val2' : TypedVal) : Bool :=
    let type1, val1 := val1'
    let type2, val2 := val2'
    match type1,type2 with
      | .int,.int => BEq.beq (α := Int) val1 val2
      | .str,.str => BEq.beq (α := String) val1 val2
      | _, _ => false

Alfredo Moreira-Rosa (Sep 24 2025 at 21:43):

And as for why it works for the separate functions ? it's because you have done the cast once when calling the function to say that the values are of the same type.

Pavel Klimov (Sep 24 2025 at 21:56):

@ecyrbe Here is closer to my actual case types:

inductive Ty
| fin (n : Nat)
| pos2d (n : Nat) (m : Nat)
deriving DecidableEq

def Ty.toType : Ty  Type
| .fin n => Fin n
| .pos2d n m => Prod (BitVec n) (BitVec m)

structure TypedVal where
type : Ty
value : type.toType

I hope you'll figure out why I dislike paired matching approach.

Pavel Klimov (Sep 24 2025 at 21:58):

ecyrbe писал/а:

And as for why it works for the separate functions ? it's because you have done the cast once when calling the function to say that the values are of the same type.

Haven't I set local variables with the same types in my first example?

Alfredo Moreira-Rosa (Sep 24 2025 at 22:42):

so here an alternative to make lean understand they are of the same type and is way shorter :

def TypedVal.eq (a b : TypedVal) : Bool :=
  if h : a.type = b.type then
    have same_type : b.type.toType = a.type.toType := by rw [h]
    let bVal : a.type.toType := cast same_type b.value
    a.value == bVal
  else
    false

Alfredo Moreira-Rosa (Sep 24 2025 at 22:48):

or even :

def TypedVal.eq (a b : TypedVal) : Bool :=
  if h : a.type = b.type then
    a.value == cast (by rw [h]) b.value
  else
    false

Pavel Klimov (Sep 24 2025 at 22:49):

it complains about line:

a.value == bVal

It says

failed to synthesize
BEq a.type.toType

And following:

def TypedVal.eq (a b : TypedVal) : Bool :=
  if h : a.type = b.type then
    have same_type : b.type.toType = a.type.toType := by rw [h]
    let bVal : a.type.toType := cast same_type b.value
    match a.type with
    | .fin n => BEq.beq (α := Fin n) a.value bVal
    | .pos2d n m => BEq.beq (α := Prod (BitVec n) (BitVec m)) a.value bVal
  else
    false

complains about both cases.

Alfredo Moreira-Rosa (Sep 24 2025 at 22:50):

add this :

private instance (t : Ty) : DecidableEq t.toType :=
by
  cases t <;> dsimp [Ty.toType] <;> infer_instance

Aaron Liu (Sep 24 2025 at 22:51):

don't use tactics to construct data

Aaron Liu (Sep 24 2025 at 22:51):

DecidableEq is data

Alfredo Moreira-Rosa (Sep 24 2025 at 22:52):

i use it all the time, why is this rule ?

Aaron Liu (Sep 24 2025 at 22:52):

you can sometimes get stuff that doesn't reduce well

Aaron Liu (Sep 24 2025 at 22:52):

or stuff that's really hard to prove it's equal to what you want it to be

Aaron Liu (Sep 24 2025 at 22:53):

or stuff that's just ugly when you unfold it

Alfredo Moreira-Rosa (Sep 24 2025 at 22:53):

i guess it's ok for my code then ? not mathlib ?

Aaron Liu (Sep 24 2025 at 22:53):

or stuff that has poor performance when compiled

Aaron Liu (Sep 24 2025 at 22:54):

ecyrbe said:

i guess it's ok for my code then ? not mathlib ?

well it's bad practice but if you really want to then

Pavel Klimov (Sep 24 2025 at 22:55):

It's amazing :+1:
I didn't know that you can do that.
But it doesn't answer my initial question :(

Alfredo Moreira-Rosa (Sep 24 2025 at 22:56):

just in case, you want to put it on mathlib, without tactics:

private instance (t : Ty) : DecidableEq t.toType :=
  match t with
  | .fin n => (inferInstance : DecidableEq (Fin n))
  | .pos2d n m => (inferInstance : DecidableEq (Prod (BitVec n) (BitVec m)))

Alfredo Moreira-Rosa (Sep 24 2025 at 22:57):

But personnally i prefer tactics automation, so i could change my types and it still would work :)


Last updated: Dec 20 2025 at 21:32 UTC