On Fri, Oct 20, 2023 at 1:07 PM Nurahmadie Nurahmadie <nurahma...@gmail.com> wrote:
> This statement doesn't feel right to me, one can always do `type NewType > struct{}` to create genuinely new types, but if you do `type String > string`, for example, surely you expect String to has `string` value, hence > there will always be a relationship between them? I might be missing > something obvious here. > I think I distinguish between a type and its representation in my mental model. A type has a semantic meaning going beyond its representation. And when I say "a genuinely new type" I mean - in this context - without implying a subtype-relationship. For example, we could imagine a world in which we had `type Path string` and `type FilePath string`, for use with `path` and `path/filepath`, respectively. They have the same representation (underlying type, in Go's parlance), but they are semantically different types and they are semantically different from a `string`. We could imagine `os.Open` to take a `FilePath` (instead of a `string`) and for `url.URL.Path` to be a `Path` instead of a `string`. And perhaps the `path` package could use type parameters to manipulate such paths, like `func Join[P Path|FilePath](p ...P) P` and provide helpers to validate and convert between them, like func ToSlash(p FilePath) Path func FromSlash(p Path) FilePath func Validate(s string) (Path, error) And we'd hope that this would give us a measure of safety. We would want the compiler to warn us if we pass a `Path` to `os.Open`, prompting us to convert and/or validate it first. To me, we certainly would not want the compiler to just accept us passing one as the other or vice-versa. Now, it is true that in Go, the representation will imbue some semantic operations on a type. e.g. the language semi-assumes that a `string` is UTF-8 encoded text, as demonstrated by `range` and the conversion to `[]rune`. And part of that is a lack of type-safety, where even with our types, you could assign an arbitrary string literal to a `FilePath`, for example. At the end of the day, Go has always been a more practically minded, than theoretically pure language. But that doesn't disprove the larger point, that there is an advantage to treating types as semantically separate entities, regardless of their representation. The way the language works right now, `type A B` really means "define a new type A with *the same underlying type* as B". In a way, the actual *type* `B` is inconsequential, only its representation matters. And perhaps that was a mistake, because it conflates a type with its representation. Perhaps we should have required the right-hand side of a type declaration to always be a type-literal - though that would open up the question of how we deal with `string`, `int`,… which currently are defined types on equal footing with what you use a type-declaration for (just that they are predeclared). Perhaps if a type-declaration would have to be clearer about the fact that it associates a completely new type with a *representation*, this confusion would be avoided. But it's not what happened (and TBQH I'm not sure it would've been better). > > >> But we could imagine having a new form of type declaration, say `type A < >> B` (syntax only illustrative) that would create a new type `A`, which >> inherits all methods from `B`, could add its own and which is assignable to >> `B` (but not vice-versa). We basically would have three kinds of >> declarations: 1. `type A B`, introducing no subtype relationship between >> `A` and `B`, 2. `type A < B`, which makes `A` a subtype of `B` and 3. `type >> A = B`, which makes them identical (and is conveniently equivalent to `A < >> B` and `B < A`). >> > > I think it's perfectly makes sense if we resort to nominal subtyping given > the new declaration, but I'm genuinely pondering about the existing > structural subtyping characteristic instead, and I'm not trying to change > anything about Go from the discussion. :D > > >> I think this would honestly be fine and perfectly safe. You'd still have >> to explicitly declare that you want the new type to be a subtype, so you >> don't get the weak typing of C/C++. And the subtype relationship would only >> "flow" in one direction, so you can't *arbitrarily* mix them up. >> >> Where difficulties would arise is that it naturally leads people to want >> to subtype from *multiple* types. E.g. it would make sense wanting to do >> >> type Quadrilateral [4]Point >> func (Quadrilateral) Area() float64 >> type Rhombus < Quadrilateral >> func (Rhombus) Angles() (float64, float64) >> type Rectangle < Quadrilateral >> func (Rectangle) Bounds() (min, max Point) >> type Square < (Rhombus, Rectangle) // again, syntax only illustrative) >> >> The issue this creates is that subtype relationships are transitive and >> in this case would become *path-dependent*. `Square` is a subtype of >> `Quadrilateral`, but it can get there either via `Rhombus` or via >> `Rectangle` and it's not clear which way to get there. This matters if >> `Rhombus` or `Rectangle` (or both) start overwriting methods of >> `Quadrilateral`. The compiler needs to decide which method to call. Usually >> it does that by defining some tie-breaks, e.g. "use the type named first in >> the subtype declaration". But there is a lot of implicity there and with >> deeper hierarchies, you can get spooky breakages at a distance, if some >> type in the middle of the hierarchy does some seemingly harmless change >> like overloading a method. Look up "Python Method Resolution Order" for the >> kinds of problems that can arise. >> >> Structural subtyping does not have these issues, because the subtype >> relationship is completely determined by a subset relationship - in Go's >> case, sets of methods of the dynamic type of the interface. And since it >> can't override methods, there is no path-dependence - any two methods sets >> uniquely determine a maximal common subset and a minimum common superset >> and the path from any interface type to any other interface is unique >> (structural subtyping is a Lattice >> <https://en.wikipedia.org/wiki/Lattice_(order)>). >> >> I think any kind of subtyping relationship *should* ultimately allow you >> to have multiple super types - these kinds of hierarchies are just far too >> common to ignore. For example, look at the `io` package - pretty much every >> combination of `Reader`, `Writer` and `Closer` has some reasonable use >> cases. I also think there are good technical reasons to avoid the >> path-dependency pitfall. So it seems to me an easily defensible decision to >> use structural subtyping as the primary form of subtype relationship, as it >> allows you to have the benefits without the problems. >> > > Structural subtyping has the most advantage in this use case, multiple > super types can be expressed with a new interface (and struct embedding as > needed) without changing anything in other packages, so it's not something > I'm going to question over again, and I do take some notes about the method > resolution issues and the insight about lattice above, thank you for the > pointer, I really appreciate it. > > >> We could do both (and disallow multiple inheritance for the nominal >> subtype relationship). But I also think it's easy to argue that this is >> redundant and a bit confusing. And ultimately you *can* express most of the >> useful hierarchies, even if you need a bit more boilerplate. >> > > Indeed I do use some boilerplate for these hierarchies, e.g. some abstract > types to have `From` and `Into` construct a la Rust. > > >> >> On Fri, Oct 20, 2023 at 8:07 AM Bakul Shah <ba...@iitbombay.org> wrote: >> >>> On Oct 19, 2023, at 9:02 PM, Nurahmadie Nurahmadie <nurahma...@gmail.com> >>> wrote: >>> > >>> > Is it not possible to have both _auto_ downcasting and new method >>> binding to work in Go? >>> >>> What you are suggesting may make things more *convenient* but >>> at the same time the potential for accidental mistakes goes >>> up. The key is find a happy medium. Not too much discipline, >>> not too much freedom! >>> >>> >>> -- >>> You received this message because you are subscribed to the Google >>> Groups "golang-nuts" group. >>> To unsubscribe from this group and stop receiving emails from it, send >>> an email to golang-nuts+unsubscr...@googlegroups.com. >>> To view this discussion on the web visit >>> https://groups.google.com/d/msgid/golang-nuts/BB7C6A9A-F0BE-4180-B495-93E4B195EA97%40iitbombay.org >>> . >>> >> -- >> You received this message because you are subscribed to the Google Groups >> "golang-nuts" group. >> To unsubscribe from this group and stop receiving emails from it, send an >> email to golang-nuts+unsubscr...@googlegroups.com. >> To view this discussion on the web visit >> https://groups.google.com/d/msgid/golang-nuts/CAEkBMfHRZLhxCM%2BMRNK7san%3D_%3DSgRF79QfZgKhu1%3Df1twWdSRw%40mail.gmail.com >> <https://groups.google.com/d/msgid/golang-nuts/CAEkBMfHRZLhxCM%2BMRNK7san%3D_%3DSgRF79QfZgKhu1%3Df1twWdSRw%40mail.gmail.com?utm_medium=email&utm_source=footer> >> . >> > > > -- > regards, > Nurahmadie > -- > -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/CAEkBMfF71z0jtFJN_h55Jm5gR%3DN3mj_g4M_z7GYN2SJvGtAc-A%40mail.gmail.com.