83 comments

[ 5.5 ms ] story [ 162 ms ] thread
This is an excellent article, also the first explanation of variance I've read that didn't make my eyes glaze over.
It is amazing what length people are willing to go in the hope of "safety".

Is there anybody out there who successfully build their own product, not via VC or other investments, but on their own time and own money who likes types?

To me it seems that types are something consultants and people who get paid for their time instead of their productivity like. Because it makes things more complex. So to achieve the same task, you can bill more time.

I have not seen anybody successfully build something on their own time, money, risk who like types. If there is someone here, I would love to see it!

On the other hand, I have seen many examples of lean code like the following turning into successful life style businesses and companies:

https://gist.github.com/nikcub/3833406

I love type safety. When I drop down to a dynamically typed language I'm actually slower as I have to double check my work. I don't understand how someone in 2023 can be against type safety.
And did you build a successful software product on your own time, risking your own money?
Yes, as everyone knows, you are only entitled to an opinion about software if you are also a successful business entrepreneur without external funding.

Good type systems increase velocity by leaving less room for error, so that you don't have to waste half your time debugging and fixing issues that could have been easily prevented. They also provide helpful structure and an IDE experience (the right values, functions, actions, etc are actually suggested for you wherever you are) that really does speed up development a lot.

There's of course always a trade-off: bad type systems can get in your way with boilerplate that you have to wrangle. But I don't think that's the typical experience.

> Yes, as everyone knows, you are only entitled to an opinion about software if you are also a successful business entrepreneur without external funding.

That's not it. I disagree with the GP, but they do have a point: coding for pay, coding for fun, and coding for your own business are all markedly different. So the question whether something works or not in one of those contexts is a valid one.

I disagree with conclusion, though. Successful products are written in whatever the authors know intersected with whatever will get the job done. If you're Chuck Moore and work on embedded stuff, you'll use Forth and will be hugely successful, running circles around your competitors who use C. The relative strengths of a static type system vs unityped language are more than offset by familiarity and experience of the authors.

The story changes a bit when your product grows and you need to hire a larger team of people to work on it. The benefits of a static analysis become more pronounced when the codebase gets bigger. However, if you somehow luck out and get a team of highly competent people, they will use whatever mechanisms are available to make your larger codebase still tractable, even if they use Lisp, PHP, or PERL.

You can bet on something that explodes (Windows Phone...) and then your product gets done in by the choice of tech. Outside of those cases, though, the tech doesn't matter that much. Whatever you do, you'll have problems. Going for a really powerful language will severely limit your choice of libraries. Going with the most common language will leave you fighting decades of accrued nonsense. Going with statically typed language (without sane macros) will lead to lots of boilerplate code and will severely limit what you can do on runtime with the code (unless you use reflection, but then you're back to untyped land). Going with unityped/untyped language will make you hunt the inter-dependencies between your modules and you'll likely become conservative with removing old code (and other forms of refactoring) as a result.

Finding a right trade-off for a tech for your product is never about a single feature of that tech. It's about a complex interplay of what you have, what you think you can get, and what you want to do.

This isn't the bar though, thats a ‘can I prototype something to MVP’ situation
My shell (interactive command line environment + scripting language) is written in a type safe language. It’s tens of thousands of lines of code (maybe even hundreds of thousands, I’ve not counted) and has been actively developed for the best part of a decade.

I’d have completely failed at this project if it weren’t for type safety because refactoring anything that long lived and that much code would have been a nightmare.

That is not a business though, is it?

So there was no risk involved like "Will I tank my future if this fails?".

What I witnessed is that makers who are willing to bet on something with real consequences dislike types as they slow down the progress and therefore the chances of success.

Why don’t _you_ come up with examples where non-VC funded but successful businesses are doing well in part because they chose to ignore types?

At this point, I’m not even sure what you are trying to prove and I’m starting to feel that whatever it is, you’re trying to prove it to yourself mostly.

So only business founded with your own money has risk involved? Lol!
I took the comma in your comment to mean “or” rather than “and”. You’re right. It’s not a business. But I am risking my own time (you could argue that time is money :P)

Your point is pretty daft though. If was relevant to self-funded startups then it would be relevant to VC funded startups and hobby projects as well. But the fact that you’re having to dismiss all these counter examples people present demonstrates that your point is wrong and you’re simply not willing to back down from your position.

Plus there’s been a wealth of companies founded on .NET and Java technologies that are older than some people on here have been alive. So it’s not like examples don’t exist. You’re not just receptive to them.

Types, like many things in life, only matter once you are in a situation where you could really use them(or would sidestep a certain kind of problem…) If you are in one of those, and more importantly don’t realize it, that is when you really pay. The daily languages that we all use(99% of us) do not utilize complex type systems and thus cannot solve complex problems.
> The daily languages that we all use(99% of us) do not utilize complex type systems and thus cannot solve complex problems.

It seems reasonable to assume that the 99% comprises the bulk of production software, which is solving complex problems everywhere every day.

The remaining 1% can be valuable and interesting but come down to a small fraction of real delivered code.

You should watch this video [1] from Martin Odersky, creator of Scala.

It shows how the entire industry has moved towards stronger typing. Whether it's existing languages like JavaScript and Python or new ones like Rust and Swift. The trend is undeniable.

I use Scala and Rust every day and I personally think they are better for startups because they allow you to rely more heavily on the compiler to verify things rather than having to check at runtime with unit tests. That means more time on new features and less time fixing bugs and writing tests.

[1] https://www.youtube.com/watch?v=7mTNZeiIK7E

I don’t disagree on the summary of your post. Especially for refactoring, types can be great.

But it is funny you chose the creator of Scala as the example. The gold plated banana cables of typing. The entire language and its endless lang mailing list discussions is a meme by now.

To be fair the new languages like Rust, Swift, Kotlin etc have all borrowed heavily from Scala in their design. So clearly it is resonating.

And his upcoming research work could be game changing. The benefits of Rust's concurrency safety without the annoying borrow checker.

That's not it. The issues Scala had were:

1. A whole lot of programmers who didn't know how to use the language effectively in 2010. Less of an issue now the patterns are well established and industry wide the level of knowledge has increased (even Java has algebraic data types now)

2. Some idiots in the community, who have now left

3. Some rough edge cases in the Scala 2 type system; fixed in Scala 3

4. Ignoring the fact that not only language but also tooling is a significant factor affecting developer experience. Extremely long compile times, weird build system, lack of good IDE support.

5. Breaking backwards compatibility with each release. This is something that businesses care a lot about.

Also the learning curve isn't great for new developers in my opinion, lots of uncommon concepts and built-in keywords which aren't googleable.

This is a big issue for a language which isn't too popular since learning it is expected.

4. There is Mill now, which is very great (and also not scala-specific)

5. AFAIK it is also solved with Scala3 with a binary compatible format.

> 5. Breaking backwards compatibility with each release. This is something that businesses care a lot about.

This is a myth. Scala has had one significant compatibility break in 10+ years. Not even Java beats that. Meanwhile the likes of Python really do break compatibility every release, and businesses continue to use them.

I think people forget just how bad compatibility was a decade ago. The compiler promised no ABI compatibility even between minor versions, which on its own could have been fine; Go and Rust don't promise ABI compatibility either. The problem was that people still used maven and sbt for dependency management, which used binary dependencies, not source like Go and Rust.

So to get a Scala project going with several dependencies, you needed all of them to agree on a specific Scala compiler version and publish JARs built against it. If even one dependency lagged behind (and many often did), you couldn't update the compiler version for your project, locking you out of new versions of every Scala library you use.

This led to the ironic situation that it was easier to use Java JARs from Scala than to use Scala JARs. If the ecosystem had used source dependencies instead of binary dependencies, this could have been avoided, even if compile times may have been horrendous. Rust walks this tightrope steadily enough these days, and Go even makes it look easy.

Finally, even though the language didn't meaningfully break compatibility until Scala 3, the ecosystem as a whole did not respect backwards compatibility. For an example I'm sure many people here will remember viscerally, Lift Web kept making substantial backwards-incompatible changes regularly even after they knew they had commercial users who faced real-world costs for churn. Even the most foundational things like how to bind template elements to variables changed completely. Adding further insult, the documentation was not nearly up to the standard that would make migration productive even for those who could afford the churn.

I loved Scala but these factors burned me hard. It remains to be seen how Rust libraries will compare, but Go libraries have already proven backwards compatible to a fault, and both build dependencies from source so ABI is not an issue. I do however appreciate that Scala taught me that a beautiful language is not enough, real world usage requires responsible commitments from projects to users. It sounds like they've learned this, but a decade too late.

> I think people forget just how bad compatibility was a decade ago.

I think you're forgetting how long ago the issues were. It was pretty bad in the 2.7-2.9 days, but a decade ago was already into the 2.10 era.

> This led to the ironic situation that it was easier to use Java JARs from Scala than to use Scala JARs. If the ecosystem had used source dependencies instead of binary dependencies, this could have been avoided, even if compile times may have been horrendous.

I don't think that would've been worth it. Binary dependencies work great given that libraries cross-build; the real issue was back when source compatibility was broken often enough that that wasn't practical.

> For an example I'm sure many people here will remember viscerally, Lift Web kept making substantial backwards-incompatible changes regularly even after they knew they had commercial users who faced real-world costs for churn.

I don't think there'll be many remembering that. Lift was big, what, 15 years ago? It was already fading by the time I started Scala back in 2011.

That's just the thing, Scala and its ecosystem had these problems right at the height of its popularity, meaning that the situation was the worst right when the most people were around to experience it, and understandably many never looked back. Even if many individuals can look past it, it's become a very hard sell to teams.

It's the same story with any kind of merit vs trust. When you have a clean slate, you can build trust based on your merit, but once you break trust, merit alone isn't enough to win it back. In this industry in particular, far too many other options are already holding and building trust.

I truly wish Scala had indeed become the mainstream industry language instead of say Go. I hope Rust polishes off its rough edges and succeeds where Scala failed and where Go doesn't go far enough. But I don't see any future where Scala regains industry relevance, not even if both Go and Rust self-destruct as well.

Meh. OCaml spent over a decade underappreciated, fixed a few sticking points like package management, and then underwent a resurgence. I'd like to think that merit will ultimately win through.
No, I meant binary compatibility. Upgrading to a newer version always required recompiling all dependencies. One dependency didn't get an update and you were essentially locked on the old version. Some projects took years to upgrade from 2.11 to 2.12 and then 2.12 to 2.13 e.g Spark. This was a real pain for us.

Source compatibility based model would be ok if Java/Scala build systems worked like Cargo/Go/Npm and compiled dependencies from sources. However, that would make compile times orders of magnitude worse, and they were already unacceptable without it. I remember our project that had about 30k lines of Scala and 300k lines of Java took about 7-12 minutes to build from scratch (and the biggest contributor of compile time was scala). And we didn't even use advanced and slow stuff like macros or typelevel programming. I remember we were seriously looking into stuff like keeping the compiler in memory between the runs (thanks Bloop) and that made it much better although it came with it's own set of new problems.

Comparably on the same (now outdated) hardware Rustc is able to compile 500k lines of all dependencies in about 28 seconds (including linking).

> One dependency didn't get an update and you were essentially locked on the old version. Some projects took years to upgrade from 2.11 to 2.12 and then 2.12 to 2.13 e.g Spark. This was a real pain for us.

Spark is a special case though - it really was just Spark, and you can't really compare that to something like Rust where as far as I know there's no equivalent. Everything else that was maintained at all put out cross-builds very quickly once the changes between versions got to be small.

6. Both the pure FP and better Java crowds think they use the language "idiomatically".
On the contrary, the power of Scala types means you are less likely to get in a case where the compiler blocks you despite your code being safe.

See the example of the article, where you don't have covariance in Go and the compiler fails. Same in Java where you have to cast (and add unsafety) to make the compiler accept your code.

In Scala when using immutable structure (the default) you have covariance and the compiler will happily let you call a function that takes a List[A] with a List[B] if B extends A.

Now that's true that you can do overly complex things with Scala types, but you don't have to.

That is an odd and cynical take.

I see a TON of open source projects (frameworks, libraries etc.) on the frontend alone that are built using TypeScript. And on the backend Golang is hugely popular and Rust is gaining a lot of traction. All in open source. Not every one of those open source projects is backed by VC money or built by consultants.

Quite an unusual take. There are millions of developers who prefer TypeScript over JavaScript for any job, including self-founded products.

But even if they didn’t exist, it’s a non sequitur. There are very good reasons for type systems regardless of how projects are founded.

You might not realize that Go has a very limited and awkwardly designed static type system that is the sole reason reason people prefer it for certain tasks over Python, which has both a dynamic, and by now an optional static type system.

Your final point, that types make things more complex and harder to develop is also flawed. Type systems exist to decrease the mental overhead and unload it from the programmer onto the compiler.

They might require more work upfront, but make software more solid, easier to reason about and easier to change. If you’re building a one-off that you will never change then a type less language might be of interest to you.

But it’s short sighted to think that you will be able to write it once and keep it running forever. Only managers who don’t code believe fairy tales like that. In reality many people sit down on a codebase, they fix things, and add functionality. When they do, they want to avoid bugs, they want the compiler to help them, they want LSP, they want to avoid null pointer dereference, double free, they want to be safer, and more productive.

They’d rather spend a week upfront instead of a day so that every one of the later changes takes a day instead of a week.

So what is an example of a self-founded project that used types and is a business success now?
https://googlethatforyou.com/?q=successful%20business%20buil...

You get the picture.

Also, types are a spectrum. C++ has types, Objective-C has types. Those alone have enough success stories to fill tomes! But of course you’re talking about strong static type systems like Haskell, Ocaml, Scala or ML, so you should read up on Jane Street. Or Mercury. Blockchains like Cardano.

Personally I think the onus is on you to prove that types are _not_ useful. Many of us enjoy types.

How long list do you want?

Pretty much all successful indie games use typed languages. Of course, there is a mix with untyped scripts in there too. But many only depends on typed languages.

That’s like asking, who is an example of a white men in Germany..
> if you ignore types they will just go away

I agree, programming is very hard. Let's go shopping.

> I have not seen anybody successfully build something on their own time, money, risk who like types. If there is someone here, I would love to see it!

Experiment: you go and search for people who have built something on "their own time, money, and risk". Find them by what they've built. First decide whether you respect what they've made, and only then check what language they decided to use to build the thing. I predict that you'll find plenty of people who have built respectable things using typed languages.

Pretty much everything I build. I find it harder using languages that don’t have type safety.
Most languages are type-safe.
Some languages are weakly typed though, which can cause confusion at the points where one type is automatically converted to another (e.g. JS, where 1 + "2" = "12"), and you need to keep type conversion and precedence in mind.

It didn't take long earlier in my career to realize that in JS, you need to be aware of the type of your variables and try and avoid any implicit type conversions.

I don't agree with your comment in general, but I think I understand where you're coming from. Many formerly loosely typed languages (JS, PHP, Python etc.) have tried to introduce stronger typing - I have the most experience with PHP, so I'll talk about that: sometimes "stronger" typing can be a pain, especially if you try to introduce it to an existing weakly typed project, or if you're interfacing with some other weakly typed system (e.g. JSON files). One example: everything is working fine, then you declare a function parameter as `string`, and suddenly you get runtime errors because one of the many callers occasionally passes `null`, and you forgot the question mark before the `string`.
So what you’re saying is that by introducing type checks, you’re actually uncovering bugs. Seems to me like proof of value right there.

What does surprise me is that adding types to your PHP code adds _runtime_ errors. With Python, type annotations are predominantly used by static typecheckers and are ignored at runtime (except from some libraries like FastAPI that use reflection to inspect the type annotations). I always assumed all dynamically typed languages that introduced explicit types worked the same way

> What does surprise me is that adding types to your PHP code adds _runtime_ errors

There are static analysers for PHP too, but yeah they’re also enforced at runtime.

I think I prefer that, if there’s going to be a non-zero risk of bad types landing in production code, I’d prefer that be an error than for it to just carry on and potentially fail later in strange ways.

That said, one of the reasons cited for PHP not having generics is the performance cost of runtime enforcement. So there are a few people out there now making the case for a generic syntax that is ignored at runtime, but that can be built into static tooling. Which I can get behind—PHPs type system is really not bad at this point, but generics would be really nice.

I would also prefer type checks to be done at runtime as well. The reason Python doesn’t do that is likely indeed the cost to performance
Well no, maybe there was a legitimate reason to pass null instead of a string (I mean, nullable types exist for a reason), and then you just introduced a bug because you didn't look hard enough at all the places your function is called (which can take some time).
If you have a null-aware type system, then you just can’t call your function with a null, so no bug?

Also, if you just annotated the same implementation that already didn’t handle nulls properly, how is it any different? Your code would just die at runtime a few lines later, with a less explicit errorz

(comment deleted)
Types are like a built-in test suite for your code. They constantly check that what you are doing is not nonsensical.

If you want a high-profile story, take Figma; it's built with C++ which is a highly type-oriented language.

Anecdotally, in the last three startups I worked (including one really tiny), the code base was / is largely in Typescript, Kotlin, Python with heavy type annotations, and small bits of Rust.

Your non-typed code still uses types, you just have to remember what they are and test functions in more ways to check that your memory and assumptions are correct.
Exactly. If not, any variable is just binary data, and has no basis for interpretability. Any program with a single branch instruction at the assembly level, has implicit types!
C# is my language of choice and the strong typing allows me to refactor and change names of (“internal”) things at a whim on even extremely large solutions with no possibility of runtime failures due to the refactoring tooling doing all the work for me and the compiler complaining if something doesn’t line up.

My view is that people who don’t like strongly typed languages have either just never worked on very large codebases, or they avoid refactors due the general impracticality of it, or they have loads of unit tests to catch runtime “type mismatches” which would have just been caught by the compiler in a strongly typed language.

Choosing an untyped language vs a typed one is a trade off of less upfront work in typing things vs future analysis capabilities of your code. And for me, the upfront cost of typing things is negligible compared to the gains for the future, which in my decade+ of experience pays itself many many times over on any non trivial sized code base.

I worked on a large JS app some years ago, before types came to that world. It worked, but only because I had a good memory of what fields and types there were (JS has types, they're just hidden and weak).

But my memory, my individual ability to keep code in my head, to keep an API's datatypes in my head, or to keep function signatures in my head does not scale. Second, it's not reliable, and needs either tests or production errors to verify.

Untyped / weakly typed languages aren't going to go away, and if you prefer them, I'm not going to convince you otherwise. I don't mind them for smaller projects or one off scripts either, utilities. But for larger applications that multiple people are involved in (directly or indirectly, e.g. an API not maintained by myself), I prefer to have a strong type system.

That said, I don't think Go's type system isn't the best, it's kinda loose in some cases (e.g. no enum types). You can't use it to bolt down your application as tightly as e.g. Java or Scala or other such languages. That's probably by choice though, since you need to stay productive as well and not masturbate over your own type system.

Yes, me. I develop an IDE for Clojure code which is a mix of Clojure and Kotlin. I like both, but what I like about Clojure is the interactive programming, not the lack of types. If there were a statically typed Clojure I’d be all over it.
There's no Typed Clojure, still? I seem to remember people porting Typed Racket to Clojure; a quick search gives me core.typed[1] and typedclojure[2]. There's an example[3] looking like this:

    (t/ann hello-world-error [t/Int :-> t/Str])
    (defn hello-world-error [a] (inc a) ;#"Simplified a bit")
which looks like Typed Racket, where you'd get:

    (: hello (Number . -> . String))
    (define (hello num) (add1 num))
(both definitions are rejected at compile time; in Racket, it works in the REPL, too, not sure about Clojure)

...and Typed Racket is a really powerful type system (see refinement types[4]). So, I thought it's just a matter of time for Clojure to get to that level of power and support. It should be much easier to do this to Clojure than to Ruby, given that you have a working example of how to do it well. So I'm really surprised Clojure isn't gradually typed by now, with most of the code being annotated and type-checked at compile time.

[1] https://github.com/clojure/core.typed

[2] https://github.com/typedclojure/typedclojure

[3] https://github.com/typedclojure/typedclojure/blob/main/examp...

[4] https://docs.racket-lang.org/ts-reference/Experimental_Featu...

Typed Clojure is still a thing and I think Ambrose is still working on it, but I don't think anyone actually uses it in practice. A few companies tried it and then backed it out, but that was a while ago now. It may be better recently but I haven't made the time to really kick the tyres on it to see what's changed. Previous it was just a pain ergonomically.
Hmm, that's sad. I guess the difference with Typed Racket is that the Racket devs and researchers behind it were all completely sold on the idea from day one, which resulted in a quick progress in annotating the whole of the stdlib. Without that, you'd need to annotate everything you require (import) yourself, which would be a massive pain. Another difference might be the module system of Racket, which is pretty unique. IIRC Clojure modules are just namespaces, similar to CL, right? In Racket, the module system along with the extensive support for contracts, allowed TR to be much more strict with types, while still interfacing well with untyped code. In effect, Racket as a whole is gradually typed, but within the bounds of TR modules there's a sound, fully static type system.

It's a shame. I thought Clojure, as the only Lisp currently used in the industry, would benefit from the great work of Racket folks the most. After all, static typing in your pet project is much less important (or even desirable) than in a production-quality software made by a team of devs under time constraints. It's strange to see JavaScript and Python getting (widely used) gradual type systems before Clojure when the foundational work on gradual type systems was done in Lisps (Scheme and Racket).

Types save time in long run

Are those successful products you are talking about small crud apps juggling jsons over https?

Idk why r u trying to act as if using types at $DAYJOB didnt matter, lol. Those are serious businesses

Yes, but why I like Go is the feel of a non-typed language, which is typed in just the right spots.

I don't like generics.

On the frontend I prefer JS over TS, because of the typing overhead of TS (both in types in amount of keystrokes).

But it's strange to connect typing and successful product. Doesn't make sense to me.

Look up EmberGen. Also every indie game made in Unity. Native macOS apps like Pixelmator. Telegram.
>Is there anybody out there who successfully build their own product, not via VC or other investments, but on their own time and own money who likes types?

Only almost every small app software house for Windows and MacOS ever, and almost evert indie game developer?

And anybody who wrote and sells of supports and makes money as FOSS, any libraries/backend tools/etc in C++ and co, so tens of thousands of projects?

In my opinion the interface system in Go is largely redundant, because interfaces are mostly equivalent to… structs of function pointers. And the single function interface case is just equivalent to a single function pointer.

If Go had a slightly better syntax for that, a lot of the interface use cases could be largely simplified. The interface implementation as subtype could be replaced with explicit instantiation using an “extends” keyword, plus sum types, and it would have been a simpler approach.

> interfaces are mostly equivalent to… structs of function pointers

This is an interesting discussion point. Some colleagues at a previous workplace actually used such structs with function fields (in Go they're not designated "function pointers") and ended up with what I called "poor man's interfaces". They're awkward to use, though in some ways they're more flexible, as you can mix and match implementations by changing any of the function fields.

However, interfaces are much smaller: only two words. Imagine if you had a 20-method function-struct and then a slice of 1000 of those. With interfaces, that would be 2000 words, with functions-structs it'd be 10x that big. (I suspect interface methods are faster to call, too.) So apart from interfaces having more first-class language support, I suspect performance is one of the reasons they weren't done this way.

All it takes is a pointer to some prototype struct shared by all instances; let's call it VMT.

Congrats, we have reinvented canonical OOP classes, in their crudest form, and without any syntax sugar. We can still make the type system give us some static guarantees for presence of the methods, and maybe the compatibility between methods we put into slots, and instances they'll have to operate on.

We're on the way of inventing ObjC, JavaScript, or C++, depending on how bad our tastes are %)

Someone just rediscoverd Oberon as originally designed in 1987!

Extensible records with function pointers.

> The interface implementation as subtype could be replaced with explicit instantiation using an “extends” keyword, plus sum types, and it would have been a simpler approach.

Sure if you want to remove one of the better features of Go, Implicit interface implementation.

The statement "An interface type A is assignable to interface type B if A’s methods are a subset of B’s." is wrong. It is not in the current language specification of Go. The author misunderstood the term type set from the Go language specification. The type set of an interface is the set of types implementing the interface and not the set of methods of an interface. If you use the right meaning of type set, the subset makes sense again.
It's not wrong, https://go.dev/ref/spec#Assignability:

    > A value x of type V is assignable to a variable of type T ("x is assignable to T") if
    > ...
    > T is an interface type, but not a type parameter, and x implements[1] T
[1]: https://go.dev/ref/spec#Implementing_an_interface:

    > A type T implements an interface I if:
    > ...
    > T is an interface and the type set of T is a subset of the type set of I
It’s backwards. For A to be assignable to B, A must implement B, so A’s methods must be a superset of B’s.
The writing style of Bob Nystrom is just awesome. He has the right balance between tech details and the wording.
One of the most insightful articles on Go in years. Great educational stuff!
> When that happens, all of the types the type checker poked at have cracked out of their chrysalides and emerged as beautiful runtime value butterflies flitting around in memory.

This sentence will forever change how I feel when debugging running programs.

(comment deleted)
The "Variance of slice types" paragraph is confusing as hell. The type Tree implements interface Barker, after all. To me it looks like this is more about what I call "accidental implementation" (which is one of the many reasons why I don't like Go). Can somebody please explain what I'm missing?
Yes, it conflates a structural vs nominal type difference (i.e. punning 'bark'), with a classic variance issue.

Ignore the tree. Suppose you have _seals_, who are plausibly 'barkers' in the same sense as dogs. Take an array Dog[] and pass it to a function that accepts Barker[]. It seems reasonable to take a Barker from the array, nice and safe. But suppose we try to put a Seal into the array, that's not fine: it's a Dog[].

Famously java got this wrong, but runtime checks (somewhat) stop that being a disaster.

It's just a bad example because the dog barking and tree barking just happened to be called the same.

But let's say you have Admin and EndUser that both implement the User class. You would think you could use a []EndUser on a function that takes a []User. Because you can use a EndUser when you need a User.

Except you can't because of mutability. In Scala for example, ListBuffer is invarient (because you could append the wrong type) however the immutable List is covarient so you can call a function that takes a List[User] and pass it a List[EndUser].

The conclusion is pretty weird to me.

Go does rely on monomorphization for generics, just like C++ and Rust. The only difference is that this is an implementation detail, so Go can group multiple monomorphizations without worrying about anything else [1]. This form of hybrid monomorphization is being increasingly common, GHC does that and Rust is also trying to do so [2], so nothing special for Go here.

On the other hand, explaining variance as a lifted polymorphism is---while not incorrect per se---also weird in part because a lack of variance is at worst just an annoyance. You can always make an adapter to unify heterogeneous types. Rust calls it `Box<dyn Trait>`, Go happens to call it an interface type instead. Both languages even do not allow heterogeneous concrete (or runtime) types in a single slice! So variance has no use in both languages because no concrete types are eligible for variance anyway.

I think the conclusion got weird because the term "subtyping" is being misused. Subtyping, in the broadest sense, is just a non-trivial type relation. Many languages thus have a multiple notion of subtyping, often (almost) identical to each other but sometimes not. Go in particular has a lot of them, and even some relation like "T implements U" is a straightforward record subtyping. So subtyping is indeed a red herring here. Given this, it is no surprise that the non-uniform value representation has the largest influence, and only monomorphization schemes and hetero-to-homogeneous adapters vary in this particular group.

[1] https://github.com/golang/proposal/blob/master/design/generi...

[2] https://rust-lang.github.io/compiler-team/working-groups/pol...

> Subtyping, in the broadest sense, is just a non-trivial type relation.

Does parametric polymorphism count as an example of subtyping? After all, it allows you to subsume e.g. Int -> Int into ∀X. X -> X.

Yes, or more explicitly speaking, parametric polymorphism involves subtyping in its typing rules. But it is not the only one. (At least in a broader sense.)
go doesn't have inheritance it just has composition, where you write the name of a parent class inside a child class, and get all its fields and methods

but you don't inherit them, and unlike some languages you type the parent's name inside the class rather than in the typedecl

generics? we won't need them. okay yes maybe not having generics will lead to an outcome where it is impossible to publish reusable code for sorting and lists, but surely if we gave people generics they would simply abuse them and live in filth

there should be a name for the type of language designer who thinks you can give people generics without ruining the package ecosystem

it is Good and Right that most lines of code are `if x, err := thing(); err { return err }`. from a language design perspective, manually plumbing errors around prevents Pollution of the package ecosystem. we can Lint the % of tokens that are `err` and if the % is low that is a bad language to which ours is superior

(comment deleted)