Zulip Chat Archive

Stream: new members

Topic: Adding an instance of LawfulBEq to my nested inductive type


Pawel Welsberg (Feb 01 2026 at 22:35):

Hi, I am still new to Lean. I'd appreciate your feedback.
I am adding an instance of LawfulBEq to my nested inductive type and I got an error:

Deriving `LawfulBEq` for nested inductives is not supported

This is when I am just using deriving keyword:

inductive File : Type
| directory (path : String) (children : Option (List File))
| file (path : String)
  deriving Repr, LawfulBEq

With a bit of help I got this manual solution (that seems to work fine):

inductive File : Type
| directory (path : String) (children : Option (List File))
| file (path : String)
  deriving Repr

mutual
  private def beqFile : File  File  Bool
    | .file p, .file p' => p == p'
    | .directory p c, .directory p' c' => p == p' && beqOpt c c'
    | _, _ => false
  private def beqOpt : Option (List File)  Option (List File)  Bool
    | some xs, some ys => beqList xs ys
    | none, none => true
    | _, _ => false
  private def beqList : List File  List File  Bool
    | x::xs, y::ys => beqFile x y && beqList xs ys
    | [], [] => true
    | _, _ => false
end

instance : BEq File := beqFile

mutual
  private theorem beqFile_eq : beqFile a b = true  a = b := by
    cases a <;> cases b <;> intro h <;> simp_all [beqFile]
    case directory.directory p c p' c' =>
      cases hp : (p == p') with
      | false => cases hc : beqOpt c c' <;> simp_all
      | true => cases hc : beqOpt c c' with
        | false => simp_all
        | true => simp_all [LawfulBEq.eq_of_beq hp, beqOpt_eq hc]
  private theorem beqOpt_eq : beqOpt c d = true  c = d := by
    cases c <;> cases d <;> intro h <;> simp_all [beqOpt]
    case some.some => simp_all [beqList_eq h]
  private theorem beqList_eq : beqList xs ys = true  xs = ys := by
    cases xs <;> cases ys <;> intro h <;> simp_all [beqList]
    case cons.cons x xs y ys =>
      cases hx : beqFile x y with
      | false => cases hxs : beqList xs ys <;> simp_all
      | true => cases hxs : beqList xs ys with
        | false => simp_all
        | true => simp_all [beqFile_eq hx, beqList_eq hxs]
end

mutual
  private theorem beqFile_rfl : beqFile a a = true := by cases a <;> simp [beqFile, beqOpt_rfl]
  private theorem beqOpt_rfl : beqOpt c c = true := by cases c <;> simp [beqOpt, beqList_rfl]
  private theorem beqList_rfl : beqList xs xs = true := by cases xs <;> simp [beqList, beqFile_rfl, beqList_rfl]
end

instance : LawfulBEq File where
  eq_of_beq h := beqFile_eq h
  rfl := beqFile_rfl

which looks good to me but seems quite long.

I just need to compare two values of a type that contains Files (So BEq is not enough, it needs LawfulBEq).

Any suggestions how to make this shorter? There is no business logic in the above code (LawfulBEq instance) and seems like it should be shorter or automated.

Eric Paul (Feb 01 2026 at 23:16):

Here's a slightly shorter version with some uses of grind:

inductive File : Type
| directory (path : String) (children : Option (List File))
| file (path : String)
  deriving Repr

mutual
  private def beqFile : File  File  Bool
    | .file p, .file p' => p == p'
    | .directory p c, .directory p' c' => p == p' && beqOpt c c'
    | _, _ => false
  private def beqOpt : Option (List File)  Option (List File)  Bool
    | some xs, some ys => beqList xs ys
    | none, none => true
    | _, _ => false
  private def beqList : List File  List File  Bool
    | x::xs, y::ys => beqFile x y && beqList xs ys
    | [], [] => true
    | _, _ => false
end

instance : BEq File := beqFile

mutual
  private theorem beqFile_eq : beqFile a b = true  a = b := by grind [beqOpt_eq _, beqFile, File]
  private theorem beqOpt_eq : beqOpt c d = true  c = d := by
    cases c <;> cases d <;> grind [beqList_eq _, beqOpt]
  private theorem beqList_eq : beqList xs ys = true  xs = ys := by
    cases xs <;> cases ys <;> simp_all [beqList] <;> grind [beqFile_eq _, beqList_eq _]
end

mutual
  private theorem beqFile_rfl : beqFile a a = true := by cases a <;> simp [beqFile, beqOpt_rfl]
  private theorem beqOpt_rfl : beqOpt c c = true := by cases c <;> simp [beqOpt, beqList_rfl]
  private theorem beqList_rfl : beqList xs xs = true := by cases xs <;> simp [beqList, beqFile_rfl, beqList_rfl]
end

instance : LawfulBEq File where
  eq_of_beq h := beqFile_eq h
  rfl := beqFile_rfl

Pawel Welsberg (Feb 01 2026 at 23:49):

Looks better. Previously File was just using deriving BEq but I upgraded to Lean 4.27.0 and I needed to implement LawfulBEq. And from 6 additional characters it went up to 30+ lines.
For some unknown for me reason it is erroring and highlighting grinding with the following:

unknown free variable `_fvar.3089`

I think it could be:
https://github.com/leanprover/lean4/issues/11930

Pawel Welsberg (Feb 02 2026 at 22:05):

Thank you @Eric Paul for your replay. I may use sorry for now to keep my code clean and simple.


Last updated: Feb 28 2026 at 14:05 UTC