Zulip Chat Archive
Stream: new members
Topic: Unexpected behavior of class fields
pandaman (Dec 16 2025 at 14:19):
I'm defining a class PositionRecorder representing a type with a distinguished element (empty) and an operation to update a value (record). My first attempt resulted in empty inheriting an explicit argument of the class, which I'd like to avoid. However, on the second attempt, PositionRecorder.empty is in a weird state where it exists and doesn't exist at the same time.
Does anyone have any idea what's going on (and how to properly define it)?
open String (Pos)
namespace One
class PositionRecorder (s : String) (α : Type) where
empty : α
record : α → Nat → Pos s → α
-- For some reason, empty requires (s : String)
-- one.PositionRecorder.empty (s : String) {α : Type} [self : PositionRecorder s α] : α
#check PositionRecorder.empty
end One
namespace Two
-- For some reason, putting `empty` in `PositionRecorder` makes `empty` require `s` as an argument.
class WithEmpty (α : Type) where
empty : α
class PositionRecorder (s : String) (α : Type) extends WithEmpty α where
record : α → Nat → Pos s → α
-- empty doesn't exist: "Unknown constant `two.PositionRecorder.empty`"
-- #check PositionRecorder.empty
-- Yet we cannot define it: "invalid declaration name `two.PositionRecorder.empty`, structure `two.PositionRecorder` has field `empty`"
-- def PositionRecorder.empty {s : String} {α : Type} [inst : PositionRecorder s α] := inst.empty
end Two
-- Example implementations
instance historyAccumulatorInst {s : String} : One.PositionRecorder s (Array (Nat × Pos s)) where
empty := #[]
record xs idx p := xs.push (idx, p)
instance overwriteInst {s : String} {size : Nat} : One.PositionRecorder s (Vector (Option (Pos s)) size) where
empty := Vector.replicate size .none
record ps idx p := ps.setIfInBounds idx p
Damiano Testa (Dec 16 2025 at 14:24):
Wrt to getting WithEmpty in Two, you can access it via #check PositionRecorder.toWithEmpty. Adding to is what tends to happen.
pandaman (Dec 16 2025 at 14:30):
Yes, we can access WithEmpty, but I'm more interested in accessing empty directly. In other words, my question is about the differences between these two:
namespace Two
-- For some reason, putting `empty` in `PositionRecorder` makes `empty` require `s` as an argument.
class WithEmpty (α : Type) where
empty : α
class PositionRecorder (s : String) (α : Type) extends WithEmpty α where
record : α → Nat → Pos s → α
-- empty doesn't exist: "Unknown constant `two.PositionRecorder.empty`"
-- #check PositionRecorder.empty
-- Yet we cannot define it: "invalid declaration name `two.PositionRecorder.empty`, structure `two.PositionRecorder` has field `empty`"
-- def PositionRecorder.empty {s : String} {α : Type} [inst : PositionRecorder s α] := inst.empty
end Two
namespace Three
-- No dependency on (s : String)
class PositionRecorder (α : Type) where
empty : α
record : α → Nat → Nat → α
-- PositionRecorder.empty exists as expected
#check PositionRecorder.empty
end Three
Aaron Liu (Dec 16 2025 at 20:04):
you can access it through WithEmpty.empty
Last updated: Dec 20 2025 at 21:32 UTC