> ... if the only takeaway from Go for you is that it is “unacceptably crippled” then I feel you have missed a lot of insight.
Perhaps the author used a poor choice of words and instead could have phrased their intent along the lines of:
Go lacks the semantic density needed to express solutions in both a concise and consistent manner.
Were this the case, it would be hard to disagree as Go does, indeed, lack linguistic capabilities present in other programming languages which enable developers to encode system constraints within the language itself. Some might see this as a benefit, but I do not. YMMV.
A similar philosophy of "keep the language dirt-simple so anyone can code in it" was a driving force behind Java (the language) and JavaScript. What people have discovered is that when a programming language does not assist in expressing intrinsic problem complexity explicitly, it becomes implicitly intertwined within the source itself.
Pre-generics Java is about the appropriate amount of language complexity for our current lingua franca
I am not sure to whom you are referencing as being "our", so I can only say I disagree as there are many developers which are quite adept in languages having more complexity than "pre-generics Java."
Perhaps you meant to reply to a different comment?
Also even Google does have more stuff going on Java, C++, Dart, Kotlin and TypeScript than Go, which has more uptake outside Googleplex.
That is a difficult statement for a Go user to parse.
It is referring to a lack of language features that result in verbosity or boilerplate?
I don't like it. I program Rust the rest of the time. I'm considering learning Perl5 so that at least my type system lets me do vaguely expressive things - I'm utterly fed up of half my code looking like:
foo, err := something()
if err != nil {
return nil, someErr{err}
}
I got very used to Rust's try!() macro, now ? operator, and being able to map over Results to convert between error types in a single line that feels much less noisy - I have functions in Go that are a dozen or so lines that would've been 3 in Rust, and tbh I struggle to follow what the function actually does when 3 out of 4 lines are to do with the failure case.I would consider "unacceptably crippled" to be a reasonable description of Go, even if it's the best tool I have for some jobs.
Or maybe they have just expanded their "knowledge spectrum" and are experiencing an effusive moment?
Those who can will do,
Those who cannot will seek to,
Hide that fact from you.
HTHBoth points you make I agree with. I simply was offering a possible explanation for those whom exhibit overly passionate opinions when they become enlightened.
When people take an epiphany to the point of denigrating those around them, then they likely have issues beyond what any programming philosophy could possibly encode.
Um, the whole point of Go was to be "opinionated and acceptably crippled". That's why the creators built it the way they did. Go has more than a few things where "Me, but not for thee" rules--the compiler can do X but you cannot. If you find that is an acceptable tradeoff for the other features of Go, great!
Lots of modern languages, however, are trying to go the other way. They are trying to give the programmer every bit of power that the library/compiler programmers have. This has its own failure modes that you may find unacceptable. That's fine too.
I personally dislike the Rust vs Go discussions. Those two languages in particular really don't have domain overlap and are like comparing apples to screws.
But of course, Rust is not flawless. In fact up until recently it was kind of annoying, before non-lexical lifetimes became a part of the language. It’s also a very large and complex language compared to Go. This is not unilaterally a bad thing, but just as every line of code comes at a cost, so to does every language feature, and if enough language features fail to pay the rent your programming language will end up feeling bloated.
What Go lacks is exactly its strengths. If you criticize C for not having Java-style exceptions, people will look at you funny. There is some divide in the community over error handling but I defend that Go’s verbose and stupid error handling pattern is my favorite part of the language, and changed how I code inside and outside of Go. I also appreciate Go’s simple but effective patterns for composition-based object oriented programming, and for having a decent concurrency model (not that it is without flaws: the limitation of Go’s memory safety is definitely an issue here.)
Everything comes at a cost. The borrow checker brings immense promise for security, but that does not mean other approaches to memory safety or language design are suddenly obsolete. It’s more complicated than that, and I consider failure to understand this to be a sign that someone is not keeping an open mind.
Surely you understand that this is a very self-serving attitude, though?
It's like someone points out that your car is crippled because it has a turning radius of 2 degrees and also has no brakes, and you go, "no but you see, what this car lacks is exactly its strengths! And it's those strengths that I enjoy the most when driving this car!"
But it's certainly less semantically dense compared to Rust or Scala etc.
type myEnum int
const (
someValue myEnum = iota
anotherValue
moreValues
)
> Sum typesConsider thinking about the types in your sum type and what they have in common, and define an interface that they share.
> Recursive types
Not sure what you mean. This is valid Go:
type tree struct {
value int
left *tree
right *tree
}
> Parametric polymorphismYes Go sucks in this area. `sort.Interface` is a good example of a workaround, but it isn’t ideal in any way.
> There is no this in GOLANG:
Is this not acceptable?
var x interface{}
json.Unmarshal([]byte(“[1,2,3]”), &x)
And if you don’t like that you get back a `[]interface{}` that happens to hold `int`s that you must type assert, just do it the better way: var x []int
json.Unmarshal([]byte(“[1,2,3]”), &x)- those are integers not enumerations. In Golang they have a type Bool which is either true or false. Can I define my own type using the same convention in GoLang? No. I can't. Ideally I want the ability to do this:
type Bool = Enum {
True
False
}
or type Tri = Enum {
True
False
QuantumTrueFalseDuality
}
But in Golang I'm locked in with what the creators provided as primitive types. I can't go deeper. Rust you can.> Sum types.
I don't think you know what a sum type is. This is what I mean by lack of knowledge. For sum type (A | B), A and B do not need to have a single thing in common and they do not need to share an interface. Think of a product type (A, B) as A and B and a sum type (A | B) as A or B.
The most glaring replacement of sum types in go are done with product types that contain errors from IO functions in GoLang. The Error type does not have anything in common with the Value it usually shadows and therefore both cannot be represented with an interface.
> Recursive types.
That's somewhat recursive. But that's a pointer. This works and is valid. Ideally though it shouldn't be a pointer but for efficiency during memory allocation this is a viable restriction. Recursive types only with pointer types.
> Parametric Polymorphism
I agree with you.
> Is this not acceptable?
No it is not acceptable. Interface is unusable until you make the type concrete with your asserts. Asserts are you executing a guess on a possible JSON permutation. In your second example you are guessing the type to be an array of INTs. What if that string was arriving over the wire as IO and you don't know what configuration it will be in? What if the typing was more complicated?
jsonString = "[1,2,True,[1,2,\"hello\"]]"
or jsonString = randomJSONFromIO()
The assert also leaves an opening for a runtime error which is unacceptable.imagine syntax like this:
type JSON :: []JSON | Struct{string: JSON} | bool | int | string | null
var x JSON = json.Unmarshal("[[1,2,True,[1,2,\"hello\"]],{}, null]")
JSON is a type represented using sums (|), products (Struct) and recursion (JSON). There is no possibility of run time errors here as you don't need to guess a specific permutation of JSON. You don't need to guess because such a type system can fully represent JSON. You see what is missing with go?I don't see the value in defining a custom underlying type for an enum. If it's for efficiency, sure. I've never had to use enums in a heavily performance-sensitive context so I don't personally relate to that.
> The assert also leaves an opening for a runtime error which is unacceptable.
You can handle errors gracefully.
// default to 0
maybeInt, _ := myValue.(int)
maybeInt, ok := myValue.(int)
if !ok {
// do something else
}
> imagine syntax like this:In my opinion JSON with multiple types like that is poor design.
Regardless, even with sum types the programmer has to write code for most possible states each value could be in. You have the same capability in Go with type assertions.
// Rust
match myValue {
int => ...,
string => ...,
// etc.
}
// Go
switch myValue.(type) {
case int:
...
case string:
...
// etc.
}An example of a product type is any type that can hold multiple typed entities, whether that’s a list, array, structure or map.
An example of a sum type is polymorphism defined using inheritance. The children of a parent type are encoded into the type itself, but the type instantiated can only take the form of one of the children.
Note that my example for the sum type is isomorphic to a typed enum. It’s literally the same concept. Sum types and product types are mathematical opposites. Every language that has algebraic data types has this concept encoded into it. Go is one of the few typed languages that doesn’t have it.
Let’s get away from the mathematics and talk about practicality. Your example of the switch and pattern matching. Let’s say myValue is an int, a string or a float.
Let’s also say that when you are coding you miss checking for the float case and you add an extra invalid case where the code checks for Double? What happens in rust or go? In go nothing happens because go doesn’t truly know the underlying type so by doing this the code compiles and you have encoded a runtime error into the code.
For rust exhaustive type checking will catch both the missing handler and the invalid case. That’s right rust will know you have missing logic based off of the type. This is a powerful feature that is encoded into most languages that have algebraic data types.
Now let’s take the conversation back to math. The fundamentals. Have you ever thought about what an Int actually is? Looks like an enum doesn’t it?
type int = 0 | 1 | 2 | 3 ...
If you really want to get deep an int is actually a recursive enumeration similar to json. type int = 0 | (int, 1) | (int, 2) | (int, 3) | ... (int, 9);
So for example ints are instantiated like this.. 0 = 0,
1 = (0, 1)
2 = (0, 2)
.
.
.
12 = ((0, 1) 2)
.
.
.
456 = (((0, 4), 5), 6)
Operations like addition and multiplication are defined in terms of inductive mappings between enumerations.This fundamentally what a type is. Any language missing the ability to define this is missing a mathematical primitive. It is incomplete. Go is a language that simply has an arbitrary enumeration (int) defined and the rest of the code needs to build off of it as there is no mechanism to define your own. Ints should be encoded as enumerations not the other way around.
Unmarshalling into an interface is really just a shitty enum; the number of permutations is finite. A type switch here is like a match but less type safe because it won’t check exhaustiveness of the branches.
No. Python doesn't have type checking. That example is a made up syntax of golang if golang had all the correct type primitives.
In this example I am defining a type in the first line, similar to how you would define a struct in golang. Then using the type in the second line.
There is a bit of an error, but it's too late to edit it now. the Struct in the type line should be map[string]JSON.
>Unmarshalling into an interface is really just a shitty enum; the number of permutations is finite.
The permutations of JSON are infinite. In the real world there are APIs describing deeply hierarchical data that have unpredictable deeply nested data structures. If I had a random JSON string generator, it's not that straightforward how you would save that JSON to a correctly typed variable in a GO program.
Unmarshalling you will have to define a new type for every permutation. It makes more sense to traverse the data structure to find it's structure rather then take a wild guess.
Either way in golang that "guess" happens at runtime so it will spit out an error at runtime, while with sum types and match you are type safe before the program even runs.
RE: Permutation, the unmarshalled value will only ever be float64, bool, string, interface[], or map[string]interface. The nested interfaces are recursively defined. It’s literally just like doing it in rust, except the compiler won’t do exhaustiveness checks for you.
Overall though the need for assert is a sign of a crippled type system.
Given that most (if not all) new applications have to ingest data from third party systems and export outgoing data to other third party systems, the need for assertions and data validation is not going away, even in strongly typed systems.
JSON is becoming the de-facto data transfer format, and thus JSON input validation is necessary in both weekly and strongly typed systems. Assuming this JSON trend continues, we can predict stronger JSON type checking built directly into languages.
There is a huge difference in guessing a type using assertions or writing handlers within the boundaries of a domain. Alex’s point that writing such code is effectively the same as rust because the amount of code is the same. However safety is lost. There is literally no point of having a type system in place when unmarshalling Json in go because you have no type safety. In go if you assert the wrong type or if your missing a required assert the error may be hidden and if it’s not hidden you can only see it during runtime. In rust there is no type assertion, only type checking, so such a program won’t even compile.
Gotta say, I lost a lot of respect for the author at this point. It’s not like I don’t love Rust - quite the contrary - but if the only takeaway from Go for you is that it is “unacceptably crippled” then I feel you have missed a lot of insight. Go has been one of my languages of choice for over half a decade now, and for good reason.