Zulip Chat Archive

Stream: lean4

Topic: Inheritance question


Patrick Massot (Sep 19 2023 at 16:21):

I have a very basic question but I'm not sure about the idiomatic solution. Maybe I'm too much used to python, but I want to be able to write a function taking as argument any object inheriting from some basic class. Ideally I would like to write

structure Enough where
  x : Nat
  y : Nat

and then define a function

def getX {α : Type _} "some way to state that α is a structure extending Enough" (a : α) : Nat := a.x

That could be applied to x : Extra where

structure Extra where
  x : Nat
  y : Nat
  z : Nat

I know I can use

class Enough (α : Type) where
  x : α  Nat
  y : α  Nat

def getX [Enough α] (a : α) : Nat := Enough.x a

structure Extra where
  x : Nat
  y : Nat
  z : Nat

instance : Enough (Extra) where
  x := Extra.x
  y := Extra.y

#eval getX (Extra.mk 1 2 3)

But this doesn't feel right. What am I missing?

Eric Wieser (Sep 19 2023 at 16:22):

I think that's the idiomatic solution, as it's what we use to generalize over subobjects and morphisms

Eric Wieser (Sep 19 2023 at 16:22):

What you (and everyone else) are missing is automation to build this automatically

Eric Wieser (Sep 19 2023 at 16:24):

Your alternative is to exploit dot notation "seeing through" extends:

structure Enough where
  x : Nat
  y : Nat

def Enough.getX (a : Enough) : Nat := a.x

structure Extra extends Enough where
  z : Nat

#eval ({x := 1, y := 2, z := 3} : Extra).getX

Patrick Massot (Sep 19 2023 at 16:25):

Yes, this is inspired by what we do in algebraic situations, but somehow it feels weirder when Extra has no parameter at all.

Eric Wieser (Sep 19 2023 at 16:25):

These correspond roughly to what C++ calls dynamic polymorphism and static polymorphism, respectively

Eric Wieser (Sep 19 2023 at 16:26):

I'm curious how Rust and Haskell solve this

Patrick Massot (Sep 19 2023 at 16:28):

I have a hard time deciding whether I should use the dot notation trick. It really feels dirty.

Eric Wieser (Sep 19 2023 at 16:28):

It also breaks Extra.mk 1 2 3, though you can fix that by writing Extra.mk {} 1 2 3 and using the FlatHack trick from my CICM paper

Patrick Massot (Sep 19 2023 at 16:30):

Currently in my code I use the solution I showed at the beginning of this thread, with a ugly derive handler.

Patrick Massot (Sep 19 2023 at 16:30):

And I really mean ugly: https://github.com/leanprover-community/mathlib4/blob/pm_widgets/Mathlib/Tactic/Widget/SelectInsertParamsClass.lean

Patrick Massot (Sep 19 2023 at 16:31):

It does the job at https://github.com/leanprover-community/mathlib4/blob/pm_widgets/Mathlib/Tactic/Widget/Calc.lean#L50-L54 but don't dare PRing this.

Eric Wieser (Sep 19 2023 at 16:34):

What's the getX in your application?

Eric Wieser (Sep 19 2023 at 16:34):

Eric Wieser said:

I'm curious how Rust and Haskell solve this

It looks like Rust does the same thing as what you did above (their trait is our class/instance)

Patrick Massot (Sep 19 2023 at 16:37):

Eric Wieser said:

What's the getX in your application?

There are simply the fields of SelectInsertParamsClass.

Patrick Massot (Sep 19 2023 at 16:38):

So my example would be closer to my actual code as:

class Enough (α : Type) where
  x : α  Nat
  y : α  Nat

structure Extra where
  x : Nat
  y : Nat
  z : Nat

instance : Enough (Extra) where
  x := Extra.x
  y := Extra.y

#eval Enough.x (Extra.mk 1 2 3)

Patrick Massot (Sep 19 2023 at 16:46):

I think the dot notation trick doesn't work anyway, it does not allow to assign a type to functions that would need to use this trick.

James Gallicchio (Sep 19 2023 at 16:48):

I think extends generates a Coe instance, doesn't it? Could you use Coe A Enough?

Eric Wieser (Sep 19 2023 at 16:48):

I think the dot notation trick doesn't work anyway, it does not allow to assign a type to functions that would need to use this trick.

These functions would just consume an Enough (the base structure)

Matthew Ballard (Sep 19 2023 at 16:49):

I would have reached for a coercion first also

Patrick Massot (Sep 19 2023 at 16:50):

Eric Wieser said:

I think the dot notation trick doesn't work anyway, it does not allow to assign a type to functions that would need to use this trick.

These functions would just consume an Enough (the base structure)

I need them to consume more general arguments, otherwise there would be no discussion.

Eric Wieser (Sep 19 2023 at 16:59):

You could also consider

def Enough where
  x : Nat
  y : Nat

class ToEnough (α : Type) where
  toEnough : α  Enough

structure Extra where
  x : Nat
  y : Nat
  z : Nat

instance : Enough (Extra) where
  toEnough e := { e with }

#eval Enough.x (ToEnough.toEnough <| Extra.mk 1 2 3)

Patrick Massot (Sep 19 2023 at 17:00):

Indeed that also works, but still feels clunky (and requires a derive handler too).

James Gallicchio (Sep 19 2023 at 17:02):

James Gallicchio said:

I think extends generates a Coe instance, doesn't it? Could you use Coe A Enough?

maybe it should autogenerate a Coe instance...... but that's something that would require core changes, I assume

Eric Wieser (Sep 19 2023 at 17:11):

I don't think automatic generation of Coe is a good idea

James Gallicchio (Sep 19 2023 at 17:13):

Then a To A B class that is isomorphic to Coe B A but without the transitivity and notational baggage :)


Last updated: Dec 20 2023 at 11:08 UTC