> A Good Solution: Constraint Based Generics and Parametric Polymorphism > A Good Solution: Operators are Functions > A Good Solution: Algebraic Types and Type-safe Failure > A Good Solution: Pattern Matching and Compound Expressions People have tried this approach. See languages like C++ and Scala, with hundreds of features and language specification that run into the thousands of pages. For an unintentional parody…
Why Go Is Not Good
241–250 of 367 posts
Re: Why Go Is Not Good
#242Earlier quoted context omitted.
>In modern systems, simplicity is a powerful debugger. I don't consider the features I mentioned in this article to constitute "complexity". I think that Haskell is a beautifully simple language, in the same way that e.g. Euler's identity is beautifully simple. The reasoning behind it may be somewhat complicated, but the result is very simple (and impressive) to behold.
So why not just use Haskell? Why didn't Haskell take over Go's niche?
Re: Why Go Is Not Good
#243Earlier quoted context omitted.
> Rust...with its arrows, angle brackets, pattern matching, etc. seemed just so complex to fit in my brain. It's 2014 already. Angle brackets, arrows, and pattern matching are 1990 level language technology. Heck, even a dynamic front-end language like Coffescript has these kind of things nowadays.
I'm not saying you're wrong nor will I argue about it as in the end it really comes down to taste I feel. To me, and perhaps to others, these things make a language harder to read/use, regardless of how pervasive...
And foreach vs for is not a leaky abstraction. Nor is pattern matching vs traditional checking and extracting the values. So their value is not geting lost on having corner cases that the equivalent "based on primitives" code wouldn't have. If anything they make the operation even more explicit.
So I think a lot of it comes down to getting familiar with them.
While something made of "first principles" might be more instantly familiar, it will be harder to reason about because of its low-level ness as the code size increases.
Case in point, assembly. It's quite primitive language with very few constructs, so it's easy to learn to use. But an actual high level "if" or function call is much easier to grasp at once than checking 10-20 lines that implement the same thing in assembly (even if you have trained yourself to see the assembly "pattern" for a function call, etc).
Re: Why Go Is Not Good
#244Earlier quoted context omitted.
> It explains how natural language is a very poor way to express programs, and how it held back science and progress for many centuries. I'm not interested in using natural language to implement the software (write code). I'm interested in using natural/technical language to create an ontology for the architecture. This is where things get gray. When I think of an architecture, I think of something that evolves over…
> This ontology evolves over time as the software, understanding of the domain, & the domain itself changes. So what does this have to do with Go or type systems? > I usually use guard clauses to protect against nulls. I rely on my tests & production monitoring systems to prove that the implementation is incorrect. And that's a bad thing. There are better tools for this job. What's the downside of encoding nullabilit…
I've found that type systems, that aren't utilizing Duck Typing, as being restrictive & causing incidental complexity when evolving the design. I don't really care if something is a categorization of something else. I usually (> 98% of the time) only care if that something adheres to an interface.
I don't like to label people in life either :-)
> I usually use guard clauses to protect against nulls. I rely on my tests & production monitoring systems to prove that the implementation is incorrect. >And that's a bad thing. There are better tools for this job. What's the downside of encoding nullability into the types?
For the web, it's not that bad. The design is constantly evolving, so most of the development period is spent completing something that is not finished.
There's no downside in encoding nullability, unless extra syntax & incidental complexity is added. It's not a big problem for me so I'd rather not have to do extra work for this feature.
> That mostly sounds good. I would want invariants to be optional, which sounds like is the case. > Every good type system lets you "opt out".
Explicitly or implicitly? I'd rather be opted out by default and opt in when I want to. Again, I don't want to do extra work or have incidental complexity.
Re: Why Go Is Not Good
#245Every single thing listed in the article can be added to Go at any point, since it currently has a very minimal feature set. Once something like generics are added, they have to support it until the end of time or risk having an unstable API like Rust did for a while there.
> Once something like generics are added The notation in id (item for generic bracketing is harder to read than other bracketing symbols, e.g. () [] and {}. Unlike those others, angles are used for comparison ops and arrow heads also. If Go ever introduces generics, the Scala-like [] notation looks cleaner and would fit into Go's existing grammar.
type Foo[T] T
versus type Foo []int
for example. type Array[T] []TRe: Why Go Is Not Good
#246Earlier quoted context omitted.
Lack of generics is part of the reason why Go is easier to learn and the Go compiler is faster than, say, Rust. Sure, generics don't have runtime overhead in Rust and Haskell but they have other costs. You always pay for abstractions some way.
Last I checked compilation time wasn't really a thing a ton of folks worry about (myself included). Faster machines and reasonably better compilers have mostly solved this problem. I'd take zero-overhead generics over a slightly faster compiler any day of the week .
Re: Why Go Is Not Good
#247Earlier quoted context omitted.
Isn't the C equivalent of a slice just a struct containing a *T, a length and a capacity?
And some methods for manipulating it (slicing it), and reference counting. And macro's for automatically ref'ing/unref'ing.
Re: Why Go Is Not Good
#248There's something very seductive about languages like Rust or Scala or Haskell or even C++. These languages whisper in our ears "you are brilliant and here's a blank canvas where you can design the most perfect abstraction the world has ever seen." But, for systems programming, abstractions suck. They always, always have a cost. When abstractions break, you not only have to deal with a broken system but the broken ab…
But if we are talking about the cost of abstractions, the biggest elephant in the room is that Go's GC is NOT optional, which makes it unsuitable for ... (1) systems programming and (2) real-time systems.
C++ and Rust do not suffer from this. And Go is not even suitable for soft real-time systems, because for that you need a GC that never stops the world - right now Go is even less suitable than Java in this regard, because at least for Java you've got the pauseless GC from Azul Systems.
> "But, for systems programming, abstractions suck. They always, always have a cost."
That's a logical fallacy, because if all abstractions suck, then why aren't we doing "systems programming" in assembly (were systems programming is whatever the definition du-jour you prefer to fit Go in)? Clearly, it depends on the project on where it can draw the line, since we are always doing compromises for gained productivity, no? And going back to the non-optional garbage collection that's not even suitable for soft real-time systems, it kind of makes the point on Go avoiding higher-level abstractions on purpose kind of bullshit.
> "It does not make it possible to make things pretty (ugh, nil)."
It's not about pretty-ness, it's about correctness - which in a language containing memory unsafe constructs that can lead to billion dollar bugs (i.e. Heartbleed), is a freaking huge deal. Rust is very innovative in this regard, because it's a systems programming language that solves many issues by means of its more advanced type system - and surely no type system is perfect, but even a single bug that's caught by the compiler, that's a bug that won't reach production.
> "It just makes it impossible (ok, really hard) to overcomplicate things."
I wish developers would stop equating "complicated" to things "I don't understand". That's not what complicated means. Here's the definition: "consisting of many interconnecting parts or elements". That Go doesn't allow certain higher-level abstractions, that's in itself a recipe for complications.
> "for tools that only need to do one thing and do it extremely well, it's either that or C. And I'm not going back to managing my own memory anytime soon"
The choice between C and Go, given that Go is garbage collected, is a false dichotomy.
Re: Why Go Is Not Good
#249Earlier quoted context omitted.
I don't agree with the GP, but if Go were very similar to C and the only big difference would be that C has no GC it would pretty easy to picture what the C equivalent of Go code would be. Exactly the same but with calls to `free()` at the end of some functions. (or preempted between instructions at unpredictable places) Don't make GC's a bigger deal then they are. They are a tool to remove the need to call `free()`…
There's also the overhead of the mark phase, which has to work out dynamically what could be worked out statically in a system with manual memory management. That's where much of the overhead of GC comes from.
Re: Why Go Is Not Good
#250For fear of disagree downvotes: I would say that many of the qualms brought up in this article are problems that are encountered fighting the language. The problem of 'summing any kind of list' is not a problem that is solved in Go via the proposed kind of parametric polymorphism. Instead, one might define a type, `type Adder Interface{Add(Adder)Adder}`, and then a function to add anything you want is fairly trivial,…
Actually using channels as a general iterator just for the sake of using the range operator is considered as an anti-pattern. The reason is not performance (although it has a cost), but the risk of leaking producer goroutines. Your example:
for v := range mt.Walk() {
if blah {
break
}
}
How will the goroutine writing into the channel returned by mt.Walk know when there are no more consumers which will possibly read from it?One way out is:
done := make(chan struct{})
for v := range mt.Walk(done) {
if blah {
break
}
}
close(done) // or defer close(done)
Picking the right cleanup is error-prone.What about errors? How will mt.Walk tell you that it had to interrupt the iteration because an error happened? Either your channel has a struct field containing your error and your actual value (unfortunately Go lacks tuples or multivalue channels).
Furthermore uncaught panics in the producer goroutine will generate a deadlock, which will be caught by the runtime, but it will halt your process. One way to do it is:
errChan := make(chan error)
for v := range mt.Walk(errChan) {
if blah {
break
}
}
err :=
The producer will use the select statement to write both to errChan and your result channel. The success of writing to errChan is a signal for the producer that the consumer exited.
However same thing here about relying on the last statement being executed to avoid a leak in case of returns or panics. Here the defer is less nice since you're supposed to do something with the error: func Example() (err error) {
errChan := make(chan error)
for v := range mt.Walk(errChan) {
if blah {
break
}
}
defer func() {
err =
Next-style methods just pass through the panics, and allow you to handle errors either by having a func Next() (error, value) or with this pattern which moves the pesky error handling outside: i := NewIterator()
for i.Next() {
item := i.Item()
...
}
err := i.Error()
First, any panic that happens inside either your code or the generator will bubble through.
Second, if you return from your loop body, you will have to provide your own error (the compiler will remind you about your function signature, if in doubt). You can return early if the iterator can be stopped and GCed out (i.e. it doesn't handle goroutines or external resources), otherwise you'd have to call a cleanup as with channels.The rule of thumb with Go should be that you don't have to do things just because they use some syntactic sugar. After a while you start to think about beauty in terms of properties not about calligraphy.
However, I do see this as a weak point of the language, which hopefully can be solved by education; after all Go is so simple to learn that you might be tempted to make it look even simpler. But the fact that the language has (almost) no magic, it means that you can actually understand what some code does, which imho outweighs the occasional syntactical heaviness or having to learn a few patterns.