Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

In the end, I feel like structural typing is one of those attractive nuisances. It sounds good on the outset, and it does provide a limited amount of convenience, but then it turns out not to be that convenient, and it removes a lot of safety and flexibility by taking intent out of the thing.


I think the majority of the convenience of structural typing can be had with explicit casting between structurally equivalent types. It eases pressure on the type checker (checking that two types are structurally equivalent is pretty quick, checking that any expression satisfies something structurally is a little harder) and gets you ultimately what a programmer wants - convenience.


Go's interfaces are a decent 80/20 answer. You pay for them, you opt in, and they sit on type of the base type system, they aren't the primitive the entire type system is based on and they aren't what the compiler works with at the most basic level.

I have on a number of occasions wished I could define an interface that could be fulfilled by a field without me having to write a method, but it's not a use case that comes up enough to bend a type system design around. (And there are enough other workarounds, like embedding a custom type and such that it's fine.) I think a lot of people underestimate just how large every last detail of every design decision looms at this level of a language. Too many languages doing something slightly convenient for a relatively small set of flashy use cases without fully accounting for or realizing the costs it imposes.

(This is a pretty hot take but IMHO Python is almost imploding itself constantly adding features that match that description.)


> Go's interfaces are a decent 80/20 answer. You pay for them, you opt in, and they sit on type of the base type system, they aren't the primitive the entire type system is based on

That's interesting because I see them as the exact opposite. You're not saving or gaining anything of significance, and it's just increasing confusion.

Penny wise, pound foolish, if you will.


I'm not sure what you mean by "not gaining anything of significance." Go without interfaces (and for simplicity let's ignore 1.18's generics for a moment) would not be a useful language at any significant scale. You'd end up with a lot of "by hand" interfaces of structs full of function pointers (or method closures), only without the compiler support.

Nor am I sure exactly what "confusion" is being increased.


> I'm not sure what you mean by "not gaining anything of significance." Go without interfaces (and for simplicity let's ignore 1.18's generics for a moment) would not be a useful language at any significant scale.

Why would go not have interfaces? Where did you make that up from?

Go would have nominative interfaces Like most other languages.


There is a fundamental tradeoff between cost-free upcasts and efficient layout, but languages could offer both options and many in between.

IMO in general: in the face of tradeoffs, moving the goal posts to let the programmer decide is an unexplored 3rd option.


> There is a fundamental tradeoff between cost-free upcasts and efficient layout, but languages could offer both options and many in between.

Not sure what the relationship to my comment is there, this tradeoff exists in nominative land, it’s got nothing to do with structural typing.


That depends on where it's used. For example, Rust's traits bounds are structurally typed, for exemple making a function that accepts a type that's Summary + Display. This "Summary + Display" type doesn't need to have a name, and in that case it's great. On the other hand sometimes you want to have two strings, one that's a name and the other the address, and not substitute one with the other. I don't think there is "a" solution, only multiple solutions and tradeoffs.


> For example, Rust's traits bounds are structurally typed, for exemple making a function that accepts a type that's Summary + Display. This "Summary + Display" type doesn't need to have a name, and in that case it's great.

That's still nominative. The bound is not named, but it's based on names, not on structure. Otherwise you couldn't have marker types in such a bound, or would always have all of them and be unable to opt out.

> I don't think there is "a" solution, only multiple solutions and tradeoffs.

I have a hard time seeing that: if you have a structure, you can always give it a name. But you can also give different names to the same structure. So a nominative type system is more flexible and powerful at the very limited cost of having to name things. And even then, nominative type systems can support anonymous types (where the name is simply implicit / automatically generated) e.g. lambdas in C++ or Rust.


I think that's structural. My understanding is the bound is based on structure, as there is no way to differentiate between a Summary + Display in a function and a Summary + Display in another. On the other hand, with Rust's enums (that are nominal), you can have enum A { Toto, Tata } and enum B { Toto, Tata }, and both with be different. A function accepting A will not accept B. A function accepting T : Summary + Display will accept any type that implements both traits.

From my understanding, nominal vs structural typing is about how you consider group of types. For structural typing, types that contain the same thing are the same. For nominal, that isn't the case.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: