Hacker News new | ask | show | jobs
by usrbinbash 958 days ago
> Go's type system is much less expressive

Please, do explain: what does "much less expressive" mean, in technical terms? What specific data modeling can I not do in Go, and what specific bugs can be caused by that?

> and it has null pointers

Yes, so? De-Referencing a null pointer in Go crashes the program, making the bug very obvious. Go made the choice to have null pointers (which do exist in silica), and avoid the complexity of languages who pretend that null pointers don't exist.

It's a tradeoff, and a very good one at that.

3 comments

> what does "much less expressive" mean, in technical terms? What specific data modeling can I not do in Go, and what specific bugs can be caused by that?

This is a good illustration of how to model data using Rust's type system in a way that gives you compile-time guarantees of correct behavior:

https://docs.rust-embedded.org/book/static-guarantees/state-...

> Because we are enforcing our design constraints entirely at compile time, this incurs no runtime cost. It is impossible to set an output mode when you have a pin in an input mode. Instead, you must walk through the states by converting it to an output pin, and then setting the output mode. Because of this, there is no runtime penalty due to checking the current state before executing a function.

> Also, because these states are enforced by the type system, there is no longer room for errors by consumers of this interface. If they try to perform an illegal state transition, the code will not compile!

> What specific data modeling can I not do in Go

You're getting into the Turing tar-pit. There's nothing you can do in Rust you can't also do in Go, technically. Hell, you can do it all in Brainfuck too, if you so desire.

The big thing, though, is ADTs. Being able to say "the value is one of 3 possible values" is a lot easier than saying "here are three values, you should use the non-zero one".

> You're getting into the Turing tar-pit.

I am fully aware of that. My question is, what specific technical problems are caused by Go not having {feature_goes_here}.

> The big thing, though, is ADTs

Except it isn't a big thing, because for the few use cases where an ADT is actually, really, really, REALLY required, they can be expressed in Go using existing language features.

https://go.dev/doc/faq#variant_types

https://eli.thegreenplace.net/2018/go-and-algebraic-data-typ...

Quote: "It seems that Go can do just fine without adding variant types, due to the challenges mentioned in the beginning of the post and the ease with which the major use-cases can be implemented using existing language features." End Quote

On the one hand, yes, this is more verbose. On the other hand, these use cases are simply not frequent enough to justify making the languages syntax and type system more complex.

Again: Yes, Go lacks many features of other languages. That is on purpose. The language is made to be simple, as in "easy to learn, easy to read, easy to refactor, easy to generate, easy to maintain large bodies of code".

That link shows the Go type system's lack of expressiveness. You have to manually include things like the default case. Rust's compiler will do exhaustiveness checking for you, and you can't just forget about it.

Also, it's not about ADT's being required. They're preferred, and Go's type system suffers for their lack. Go is a living, breathing example of the blub paradox in action.

> That link shows the Go type system's lack of expressiveness.

And I ask again what that is supposed to mean in technical terms, and what specific problems would be prevented if Go's type system was "more expressive".

> They're preferred

I am fully aware that for every given language feature, there are people who prefer that feature. And many languages reacted to that by including everything and the kitchen sink. That made many languages very "expressive", but that expressiveness comes at a cost: It also made the languages themselves become bigger and more complex.

Go isn't about having as many features as possible though. Quite the opposite, it's about having as many features as necessary, and as few as possible. Why? Because it keeps the language small and easy to learn and the code easy to read and maintain.

And to me (and judging by the sucess of Go I am not alone in this), that is a lot more important than a bit more "expressiveness" in things that one may come across every now and then.

What it means in technical terms is what I just told you: Rust's compiler will not let you forget to handle an enum variant. Go's compiler can't do that because the type system can't handle it.
> Go's compiler can't do that because the type system can't handle it

Correct. And that isn't a disdvantage.

The fact that Go's type system "cannot handle that" directly in it's type system (functionally, the language itself can handle that easily enough, as shown by the article I linked above) is a choice, which results in a simpler language.

And I still haven't seen any specific problems that would be prevented if Go included this feature directly in the type system. I have, however, seen a lot of extra syntax that Go doesn't need to support in its compiler, Go students don't have to learn, and Go devs don't have to worry about.

> Please, do explain: what does "much less expressive" mean, in technical terms? What specific data modeling can I not do in Go, and what specific bugs can be caused by that?

> Yes, so? De-Referencing a null pointer in Go crashes the program, making the bug very obvious.

At runtime. i.e. production. You think that is just as good as solving the problem at compile time? I certainly don't.

> At runtime. i.e. production.

At runtime, i.e. initial testing, pre-commit-checks, integration-testing, QA and then production, yes.

So there are a lot of checkpoints where the system can crash before it ever goes live.

> You think that is just as good as solving the problem at compile time?

No, I don't, and I never wrote that I do.

I do think that it's a lot better than not crashing and running inti undefined behavior when dereferencing a null pointer, which is a problem in older languages, and a source of hard to track bugs.

The problem is: solving it at compile time isn't zero-cost.

Languages that pretend that void pointers don't exist are usually more complex than languages that accept their existence as a fact of the underlying hardware. That extra complexity comes at a tangible cost in development time and maintainability. A language that fails early, and with a clear signal, will sometimes crash in testing, and maybe maybe maybe in production here and there, and such crashes may incur a cost. A more complex language will always incur a higher cost in developer time.

The higher cost in developer time is a myth. Because the type checking seriously helps you out when you are changing existing code and it results in much fewer bugs out of the gate.

Sure the language in its totality is more complex. At least compared to Go or C. But nothing is forcing you to use every feature of it.

> The higher cost in developer time is a myth.

And yet, Go is a huge success in the industry, and one of the main reasons often given for chosing Go over a competing language, is how easy it is to get things started, how easy it makes the onboarding process, and how accessible and maintainable the code is.

> the type checking seriously helps you out when you are changing existing code

If you have Algebraic Datatypes in your codebase, which is not a given. Having a simple, easy to read language helps me out ALL THE TIME.

> But nothing is forcing you to use every feature of it.

And nothing forces C developers to use so many macros that their libraries resemble a completely different language where everything I thought I learned about C flies out the window. Nothing forces Python devs to use nested dictionary-comprehensions, that are, ironically, completely incomprehensible. Nothing forces C++ developers to use generics everywhere, regardless of whether they are actually needed or not.

And yet, that is, unfortunately, what is often happening in the wild.

The point here is; If a language offers X, then X will be used. And it will be used in smart ways, it will be used in unnecessary ways, and it will be used in not-so-smart ways. It will be used when it makes sense, and when it absolutely doesn't.

The thing the developers of Go figured out, and which, in hindsight, is surprisingly obvious, is that there is exactly one, and only one, foolproof way to prevent that from happening: By not having X in the language.

You're a zealot and your comment is a polemic, not any kind of reasoned discussion of the advantages and disadvantages of one language as against another.
This isn't a discussion about the advantages or disadvantages of languages.

This is a discussion about whether Go "misses" things, or "lacks" things, or if it's designed to not have these things, and if there is a reason why that is.

I have demonstrated, and provided arguments, for why the latter is the case. Aka. the exact opposite of "polemic".

> And yet, Go is a huge success in the industry

Success of a technology is more often than not dependent on who is behind it. Anything by google will likely see a lot of uptake.

Every language has a story. Some of it is bull some not. Best to try and be objective and not drink every drop of the koolaid.

> Anything by google will likely see a lot of uptake

Disagree.

https://www.failory.com/blog/google-failed-products