Hacker News new | ask | show | jobs
by mustpax 4367 days ago
There'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 abstraction itself too. (Anyone who has ever seen a gcc compiler error for C++ knows how this feels.)

Therein lies Go's value proposition. It does not make it possible to make things pretty (ugh, nil). It just makes it impossible (ok, really hard) to overcomplicate things. When you write Go code, you can picture what the C equivalent would look like. You want to deal with errors? Here's an if statement. Data structures? Here's a struct. Generics? Here's another if statement, put it inside your for loop.

Obviously, Go is not the right choice of language for most things. When you're doing application development, you may be able to afford the cost of abstractions. But 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.

10 comments

> When abstractions break, you not only have to deal with a broken system but the broken abstraction itself too. (Anyone who has ever seen a gcc compiler error for C++ knows how this feels.)

Using C++ template error messages to attack generics in Rust and Haskell is pretty weak, because typeclasses were explicitly designed to avoid the problems of "ad-hoc" templates in languages like C++. Error messages are in fact what typeclasses are really good at.

Type classes are so good at error messages that they were proposed as the solution to the error message mess in C++ in the form of Concepts.
As someone who's spent more time than I'd like to remember improving type class error messages, this sentence boggled my mind. Then I remembered what C++ template error messages are like and it all made sense again.
There's no question that Rust and Haskell have better tools than C++ for abstracting code. Go is just demonstrating that you can write great software without the aid (and cost) of generics.
What is Go demonstrating about programming without generics that Lisp, Java (pre-2004), Python and tons of other languages haven't already demonstrated?
Nothing, thats the point.. its not language that the creators did because they are vain, or want to prove they are smart, or know what beauty is.. its a language that are created to get things done! the beauty of it its the same beauty that we see in Unix or C.. its simplicity..

Dear God.. I dont know why so many programming languages anyway to do the same thing all over again.. just because of the sake of the sintax or the type system.. or because the guy fell sooo smart because hes using FP.. so he can fell instelectually superior to all human beings

Since C.. its all the same programming paradigm.. the rest is just detail.. the only langs that have its own way that are not cover by the C paradigm are Lisps

Really my language of dreams.. will be to use notes like in a music sheet.. this is a really different paradigm.. or use DSP with just I/O signals.. this is something new.. the rest is just vanity..

And i dont want to be the rat lab of some language designer full of himself, that doesnt think of me, the poor programmer that has to maintain the code in the lang he creates!!

This is the unix philosophy... theres too much noise, im sure these are the kind of things that make people run away from technology...

We need to somehow find our way to simplicity.. for our own sake

>its a language that are created to get things done

And the hundreds of other languages serve what purpose then ?

Im sure some languages, are created just for the sake of sintax, others to prove a marginal point, and others because the author want to be a tech celebrity.. this is "the noise" i was refering to..

We can spot really important languages, that actually put something new in the table.. with real originality and genius.. and the immitators that follow

The clear evidence to what im talking about, is that if you think in a niche that you wnat to create a program, you will have 3 or more langs to choose..

Choice is good? not always.. because once we create codebases in the langs we choose we are trapped..

All the languages we heard of, create programs somehow, otherwise we wouldnt know about.. but some are more pragmatic than others..

My sentence was to explain that the authors of the language were thinking more in the enginnering aspect, than in some theoretical or research

There are a lot of research languages out there... so in my point of view, they were not created with a more pragmatic goal..

They are cool and will push the envelope.. sure! but i dont understand the elitism here, trying to bash things that were created thinking more in the enginnering axis.. while puttting some "not working yet" but "research using the poor programmers as rat labs" in a pedestal..and in the same level as something that works, because it were design to work in a more conservative matter

Is there demonstrably great software written in Go yet? I've seen some people using it but I've yet to see a "killer app" (the way e.g. MLDonkey convinced me I should take OCaml seriously, or Pandoc convinced me I should take Haskell seriously).
A few spring to mind. Such as Docker and large parts of Google and CloudFlare.

However I think your benchmark for a language's worth is a pretty superficial one.

Docker.io?
Where is that great software that breaks new ground?
Note that the cost of interface{} is, essentially the same cost java imposes.

Luckily it's used less, but that doesn't change it really. Anything becomes a double pointer indirect.

And just for completeness : even java has better tools for abstractions.

These two statements made me cringe:

> But, for systems programming, abstractions suck. They always, always have a cost.

> Generics? Here's another if statement, put it inside your for loop.

If you care about speed (and many systems programmers do), this is exactly the opposite of what you want to do. Unlike your proposal of putting potentially-costly if-statements inside of for loops, generics/templates in c++ provide zero-cost abstraction (in terms of execution time. If you think dealing with the error messages presents too high cost in terms of developer-time, switch to clang).

If you truly care about speed you'll have different optimizations for int32 and int64.

Depending on who you are working with, the lack of generics is a blessing. Some developers can't restrain themselves and create over-complex abstractions that are used only once.

Generics generally simplify code, you know. Plus, generic code tend to make guarantees non-generic code cannot.

Picture this function:

  foo :: (a -> b) -> (b -> c) -> (a -> c)
What does it do?

As you can see this function takes 2 arguments, which appears to be functions. It also returns a function. The argument and return type of these functions are unknown, so you can't manipulate them. You can just pass them around directly. This puts really tight constraints on your code. So, assuming nothing fancy happens, there is only one correct body of code for this type signature:

  foo f g = \x -> g (f x)
In other words, function composition.

---

Generic functions have more guarantees than non-generic functions. Therefore, you are more likely to know what a generic function is actually doing.

Parametric polymorphism does not simplify code, it obfuscates it as now you have to see _what_ is calling it. A better form IMO is restricting the types allowed as it: 1. makes understanding much easier, 2. Doesn't create bloat in the form of n copies for visible symbols.
> Parametric polymorphism does not simplify code, it obfuscates it as now you have to see _what_ is calling it.

This is backward. A function, polymorphic or otherwise, does not influence code that does not call it. Modulo far-reaching side effects of course.

It's when you look at the call site that you have to figure out what this strange `fold` function could possibly be about.

I'll grant that polymorphic functions are often more abstract than monomorphic ones. But they are simpler. They are also better at separating concerns. Take `map` and `filter` for instance. They capture common iteration patterns, so you don't have to entangle that pattern with the actual logic of your loop. Without parametric polymorphism, you could not write them (more precisely, you would have to repeat yourself over and over).

> A better form IMO is restricting the types allowed as it: 1. makes understanding much easier

That's just false. If you restrict the types a function can operate on, you allow the function to do more things to its data. The more you know about its input, the less you know about the function. With parametric polymorphism, you are actually hiding information from the function, preventing it from making whole classes of mistake. Free tests!

Parametric polymorphism makes functions that use it simpler (as in, less moving parts). How could that possibly be harder to understand? Please give a concrete example, I don't understand where you're coming from right now.

> 2. Doesn't create bloat in the form of n copies for visible symbols.

That's an implementation detail, and mostly false anyway. Not every language is C++. Most languages that make use of parametric polymorphism don't duplicate code.

> A function, polymorphic or otherwise, does not influence code that does not call it.

Obfuscate does not mean influence, it obfuscates it you now have to follow the rabbit hole to where the type information is (language detail but it is how almost all parametric polymorphism is) which makes reasoning about it a pain.

> That's an implementation detail, and mostly false anyway.

No it's not 'unbound' parametric polymorphism in a compiled language has to produce symbols for any visible (not internal) function as there would be no way to know what might get called.

> Most languages that make use of parametric polymorphism don't duplicate code.

Yes any compiled language does how on earth would you call a symbol that took a bool vs a size_t (I recommend you look at how ELF works). On somewhat related note a sufficiently smart compiler could 'deduplicate' common parts of the slow path within a generic function and create calls but that's about all it could do for deduplication without performance hits.

> If you truly care about speed you'll have different optimizations for int32 and int64.

This is exactly why c++ allows template specialization, and if you don't care for hand-optimizing, you can get both implementations almost for free.

> Depending on who you are working with, the lack of generics is a blessing. Some developers can't restrain themselves and create over-complex abstractions that are used only once.

I can't comment on the competency of your coworkers, but I certainly see how Go could be useful in situations without the kind of performance-constraints which demand a language like c++.

> If you truly care about speed you'll have different optimizations for int32 and int64.

I'm pretty sure that's what C++ templates specialisation is for...

Which in c++ you can easily do, as you probably know. All you need to do is have an if statement that compares the type parameter of your template to int32 and int64. The good thing is that this kind of optimizations is transparent to the user if the code works. Even Haskell has something similar, with the SPECIALIZE pragma. Although in this case you have to trust the compiler to come up with the optimizations.
Exactly! Go makes the cost of generic code explicitly visible.

Generics encourage over-generalizing behavior that runs counter to writing highly performant code. If you care about speed, you don't spend time making your code generic. You optimize closely to your use case.

>Go makes the cost of generic code explicitly visible. Generics encourage over-generalizing behavior that runs counter to writing highly performant code.

I think you may be missing some info regarding generics in Rust and Haskell. As I mentioned in the article, there is zero runtime overhead for generic programming in Rust and Haskell. Zip. Zilch. Nada. That's why their constraint-based static generics system is awesome.

Haskell's generic programming constructs do not have zero runtime overhead (at least on GHC, the dominant compiler). Typeclass-overloaded functions take an extra argument at runtime, in which the class "methods" are looked up. In particular, the typeclass-based definition of add3 turns into this Core code, which has an extra "$dNum_az0" arg to carry Num methods:

  ghci>let add3 a b c = a + b + c
  
  ==================== Simplified expression ====================
  GHC.Base.returnIO
    (GHC.Types.:
       ((\ (@ a_ayZ)
           ($dNum_az0 :: GHC.Num.Num a_ayZ)
           (a_ayG :: a_ayZ)
           (b_ayH :: a_ayZ)
           (c_ayI :: a_ayZ) ->
           GHC.Num.+ $dNum_az0 (GHC.Num.+ $dNum_az0 a_ayG b_ayH) c_ayI)
        `cast` ...)
       (GHC.Types.[]))
Compare this to the Int-specialized add3, which does not have to be passed the extra $dNum_az0 argument:

  ghci>let add3 a b c = a + b + c; add3 :: Int -> Int -> Int -> Int
  
  ==================== Simplified expression ====================
  GHC.Base.returnIO
    (GHC.Types.:
       ((\ (a_azj :: GHC.Types.Int)
           (b_azk :: GHC.Types.Int)
           (c_azl :: GHC.Types.Int) ->
           GHC.Num.+
             GHC.Num.$fNumInt (GHC.Num.+ GHC.Num.$fNumInt a_azj b_azk) c_azl)
        `cast` ...)
       (GHC.Types.[]))
Now, am I saying the the typeclass method isn't fast, or that GHC can't then optimize that Num dictionary away via specialization or inlining? No, I am not saying that. But it certainly doesn't always do that, resulting in a performance hit at runtime. More info @ http://www.haskell.org/haskellwiki/Performance/Overloading
If you have an explicit type signature and compile with -O, GHC will auto-specialize, afaik.
Please correct me if I am wrong about boxing in Rust, but doesn't boxing produce some overhead, by creating extra heap allocations (and therefore possible memory fragmentation and almost certainly poor cache locality), as well as pointer dereferences?

I really hate it in Java when I need an array of bytes but don't know the size in advance, or the size changes, and I have to incur all the cost of boxing those bytes up. On 64-bit systems (most of them), pointers are 64 bits which is at least twice as large as the most common things you put in a list (int, float, byte).

Boxing is heap allocation. Rust lets you explicitly choose if something is on the heap or on the stack. Idiom prefers stack allocation.

You don't need to box something to make it generic.

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.

> Lack of generics is part of the reason why…the Go compiler is faster than, say, Rust.

The speed of the Rust compiler has little to do with generics and everything to do with LLVM and its optimizations and code generation. (Run with -Z time-passes if you don't believe me.)

I don't know if it has to do with generics, but you can't blame it on LLVM.

    /usr/src/rust/src/libsyntax % time /usr/src/rust/x/x86_64-apple-darwin/stage2/bin/rustc lib.rs -o /tmp/x.dylib --crate-type dylib -C prefer-dynamic -Z time-passes
    [most output removed...]
    time: 6.186 s	type checking
    time: 5.084 s	translation
    time: 7.221 s	LLVM passes
    /usr/src/rust/x/x86_64-apple-darwin/stage2/bin/rustc lib.rs -o /tmp/x.dylib    22.44s user 0.76s system 99% cpu 23.301 total
First of all: no more than 1/3 to 1/2 of the time is spent in LLVM in this particular example. Okay, that's cheating because there is no -O, as I'm guessing your statement supposed, but 22 seconds is already a huge amount of time to compile 30,000 lines of code, so unoptimized builds are relevant to claims that rustc is slow. The other points apply regardless of optimization setting.

Second: rustc will take this long every time to recompile libsyntax every time anything in it changes. If this were written in C, most changes would only require recompiling one source file, even though, again, header files are not treated well (something that Rust does not need to replicate). In practice this means that typical latency between changing something and seeing the output in a C/C++ program is an order of magnitude faster.

Third: The same separation that makes incremental compilation work in C/C++ allows parallelism (make -j) in full builds. rustc uses only one core per crate. Again, headers reduce the gains but Rust doesn't have that problem.

Fourth: If we compare to C rather than C++, we're off by sometimes an order of magnitude regardless of parallelism. Here is some random C program (Apple as), a total of 26929 lines, compiling in 0.90 seconds:

    clang -o foo -I ../include -I ../include/gnu -I. -DNeXT_MOD -DI386 -Di486      0.73s user 0.16s system 98% cpu 0.904 total
(With optimizations it is 2.426 seconds.)

Or libpng, 32433 lines in 0.83 seconds:

    clang -o png *.c -I. -lz  0.70s user 0.12s system 98% cpu 0.831 total
With the Linux kernel, compiled without -j and at -O1 using GCC (both of which make it slower), it's not an order of magnitude off, but it's still significantly faster than Rust: make ARCH=arm zImage 540.25s user 73.71s system 96% cpu 10:33.81 total

It compiled 1572713 lines of code; normalizing to 30,000 gives us about 12 seconds.

On the Rust side, linking libstd, about the same size, takes 6 seconds, but librustc, three times the size, took 216 seconds (10 times as long as libsyntax) to get to linking. So it varies, but I guess libsyntax is representative.

I do not know enough to have a definitive opinion of why this difference exists, but judging from the difference between C and C++, I wouldn't be surprised if it were related to Rust's complexity, which includes generics.

Based on this logic I assume you code in nothing but machine code? After all even assembler is an abstraction and therefore must have a cost. Heaven forbid you should do something as extravagant as use C for something, that level of abstraction (all those long jumps and stack manipulations, oh my) must positively destroy performance. Do you also happen to program using the gentle flapping of butterfly wings to disturb air currents and redirect cosmic rays?
The rust compiler is actually really quick. The LLVM optimization passes and code gen is where most time is spent (this is after monomorphisation of generics). The Go compiler does very few optimizations compared to LLVM, which leads to faster build times but slower code.
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.
(incremental) compilation needs to be fast enough. A too-slow build cycle can really kill productivity, both in trying out new ideas for code and in debugging. The build times of the rust compiler are pretty close to the limit of what I'll tolerate, and I would really love to have compilation speeds similar to go.
Speak for yourself - slow compilation drives me crazy, as it increases the latency between making a change and seeing the result. Not that I believe that generics require slow compilation.
Fast build time is explicitly something Go was built for, to fix 40 minute compile times.
The dmd compiler for D compiles faster than gc and yet supports compile-time templates.
This is incorrect. Generics in C++ are zero cost, since they are specialized at compile time. On the other hand if you want to write generic code in Go you have to use Object types everywhere. That means that objects have to be tagged, those tags checked with run time checks, additional pointers everywhere, bad memory layout, etc. So generic code in Go is significantly slower than in C++.
It depends on how you quantify cost. There isn't any performance cost, yes (which is what 'zero-cost abstractions' usually means in C++), but there is a) an increase in code size, and b) an increase in complexity/difficulty of understanding of implementation (and to some extent use). These may be good tradeoffs to make (in many of the areas where C++ is used they make sense), but I think 'zero-cost' is a disingenuous way to put it.
How are generics more difficult to understand than hacking your own mechanism together with casts? That is something I do not understand.
That's the thing. It's easier to hack something together with casts that looks like it's okay, and not understand that you're doing something wrong.

With generics, the novice finds cases where the compiler catches them doing something wrong, so they rewrite the code using casts in a way that looks right but is subtly wrong.

The end result is code that appears correct to the novice, the novice walking away with the feeling that generics are too complicated, and a mysterious corner-case bug that bites off someone's arm once every five years.

The nature of failing to understand is that the person who fails to understand often fails to understand that they misunderstand, or often misattribute their misunderstanding. The tool gets blamed for getting in their way of writing "good" code. On the other hand, there are plenty of tools that give perfectly sensible error messages to anyone with a PhD in type theory, but a second year university student sees "Attempt to cast non-monoid endofunctor to monad. Please uninstall compiler and shave off neck beard."

There might actually be a performance cost due to increased code size (by way of increased amount of icache misses).
The implementers of MLton, an ML compiler that does C++ style monomorphization, found that after optimization the code size is actually smaller with monomorphization. That's because the specialized code is simpler and can be further optimized. So even if you have multiple specialized copies, the total is still smaller. See here: http://mlton.org/Performance
Doesn't this assume that compiler complexity/work is not a cost?
>But, for systems programming, abstractions suck.

Could you clarify what you mean by "systems programming"? To me, that means working with embedded systems, which Go is certainly not appropriate for.

> Could you clarify what you mean by "systems programming"? To me, that means working with embedded systems, which Go is certainly not appropriate for.

The blunt but approximately correct version is that embedded means that you're running on hardware that isn't powerful enough to run a Linux kernel.

Systems programming just means you're working below the application layer. So if you take your laptop and write a device driver, or work on filesystem or networking code, you're doing systems programming without doing embedded.

Just curious : how does one run go without a linux kernel ? (without a kernel at all, please, I know about the freebsd port)
You'll probably be looking for a Go runtime that fills a kernel shaped hole. Without a kernel, where do all your syscalls go?
Yes but the point of the parent poster was that go can work on minimal embedded systems, which to my knowledge it cannot, so I enquired.
No, the point of the parent poster was that "systems" is not the same as "embedded", and that while go may not work on the latter it can work on the former.
Systems does not necessarily imply embedded. I work in systems and, as far as I am aware, the term simply means software which primarily services the hardware (as opposed to the user). Drivers, HW interfaces, anything which has to make assumptions about the underlying hardware it is running on/servicing.
By systems programming I mean writing the code that applications and distributed systems run on top of. Raft (https://github.com/goraft/raft) and groupcache (https://github.com/golang/groupcache) come to mind as examples.

That's a good point though. A lot of people mean different things by systems programming.

> A lot of people mean different things by systems programming.

Actually, no. It meant one thing until Go proponents tried to market their language and realized that their target audience didn't actually care.

Several of the initial Go designers were doing "systems programming" in the 1960s under the exact same meaning.

I'd also s/didn't actually care/got confused/ in your last sentence too.

Doesn't Go have a GC? How can you then "picture what the C equivalent would look like"?

Yes there are GCs for C, but is anyone successfully doing "systems programming" (whatever that may be) in C with GCs?

> Yes there are GCs for C, but is anyone successfully doing "systems programming" (whatever that may be) in C with GCs?

Actually, yes. But it's hackish (relies on some pretty complex macros) and requires you to adapt certain conventions. Still, it's doable and a whole lot safer than managing your memory directly in terms of leaks and re-use after free. The cost to me really is that macro magic, that should not be required but it's the only thing I could think of to make this work. To give you an idea of just how ugly this is I re-defined 'return'. Any C hacker will be able to deduce the rest from that one hint ;)

On another note, I felt - and feel - that this was not the proper solution but the various policy choices made this pretty much the only way in which it could be done. And it works.

You rolled your own? Why not use the Boehm conservative collector?
In three letters: NIH. Management decision was that all IP had to be 100% owned by the company and had to be in 'C', in spite of an enormous amount of friction between C and the project as well as a bunch of work by others that could have been leveraged if we had decided to use code from other contributors. I got called in long after these decisions were made and it was very clear they weren't going to budge on those. There is a lot more to this story but I'm not at liberty to tell. Let's just say I learned a lot.
So you had to write your own compiler, operating system, and runtime libraries too?

-- I know, you didn't think it made any sense either. I'm just pointing out that a line has to be drawn somewhere; where it gets drawn is actually arbitrary.

Yeah, I've had to deal with ridiculous mandates from on high too, though none anywhere near that onerous. In my previous job, we were writing a compiler. It was mandated to be in C++ -- the first mistake -- and we had to use smart pointers instead of GC -- also a mistake. But the completely idiotic thing was that we were not allowed to declare any exception classes. The VP of Engineering -- a very smart and experienced but very arrogant guy -- had seen exception hierarchies get out of control before and decided the solution was to ban them.

But that's on a pretty small scale compared to what you're talking about.

> So you had to write your own compiler, operating system, and runtime libraries too?

I tried that line and it did not work.

Reminds me of my last^2 job: we were using Scala and the "technical architect" made two decisions that were mildly wrong in isolation but interacted rather badly: we'd make heavy use of monads for core functionality, and we wouldn't use scalaz. So I spent a while reimplementing parts of scalaz, and learned quite a lot (though I doubt I was adding much business value while doing so).
So, the implementation was "conservative", but on a very different level.

Sorry, could not resist.

No, no need to apologize. It's one of the strangest assignments I've ever had and there were a few twists to the whole story that would make for a good book.
That's true. It's not just GC actually. Slices and goroutines don't have a direct analogues in C either. But it is fairly easy to reason about the runtime complexity of these conveniences.

But like I said, if I didn't care about GC or concurrency, I'd be writing C.

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.
Reference counting? I didn't know reference counting was used with slices, I thought they just used the GC.
Yes, but since C is not a GC-language, I thought I'd add that to reach more equivalence :).
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()` at the right time, with the downside that you don't get to control what the GC thinks is a right time instead.

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.
I'll add to that that the overhead is not proportional to the amount of garbage your application generates, it's proportional to the amount of data blocks your application has allocated. Garbage collection is also often performed by suspending the application (stop the world) at unpredictable moments and with an unpredictable duration. Garbage collection can thus be a serious problem for some type of applications. This is why the GC should be optional.
> They are a tool to remove the need to call `free()` at the right time, with the downside that you don't get to control what the GC thinks is a right time instead.

That's not actually true. They also allow you to do things that you otherwise couldn't. Try implementing persistent [1] maps or sets without a GC.

[1] http://en.wikipedia.org/wiki/Persistent_data_structure

That's still just a problem of deciding when to call free.
Sort of. But you can't statically decide it. In fact, really the only way to do it is with a GC (or ref. counting, etc.). You can't know until runtime when a node will need to be freed.

And my point still stands. The GC allows you to do things you otherwise couldn't.

I'm not writing the following to make people pick another language - if Go is suitable for your project in terms of features, runtime, tools and community, then by all means use Go. It's a fairly decent platform to target and it can further evolve to meet more stringent needs.

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.

>I wish developers would stop equating "complicated" to things "I don't understand".

The argument is a bit more about evolved than that, but flawed nonetheless. The argument usually invoked for Go is that, by eschewing selected language features, it prevents developers shooting themselves in the foot with unneeded complexity introduced by faulty abstractions.

I get the argument. I have seen my fair share of dug-out-from-hell complex projects. What the argument misses though is that not all abstractions are faulty. Computer science, as most science, is a game of ever increasingly abstract reasoning. If implemented correctly, the more abstract the better. The endgame is "Computer, build me a Mars round-trip ship".

Abstractions are good, when well written. They allow us to think on a higher level. Think of them as a fixed learning cost replacing a variable development cost.

Go kind of throws out the baby with the bathwater.

    > The endgame is "Computer, build me a Mars round-trip ship".
I think nobody would argue against that endgame, in the broad strokes. But I think Go can be understood as a response to [what the Go developers perceive as] overly expressive languages, languages that have overstepped our ability to responsibly abstract details, languages whose abstractions hide details that are still important and necessary to make explicit.

Go is "lower level" in that it purposefully eschews those abstractions, but I think it does that successfully, without a significant loss in expressivity, and with a more-than-commensurate gain in understandability, maintainability, performance, etc.

I agree. That is a correct reading. Where I do not agree is on the assumption that a basic feature set can be construed as a simpler language. While true for the language proper, it isn't true for real usage of language plus libraries.

Take operator overloading. It can be used to create hellish code. It can also be used to create great libraries (numpy for instance). Because of the danger of hellish code, Go makes it impossible to create numpy in Go.

In the end, while Go is simpler, a numpy in Go would be more complex[1], because the language is not expressive enough. The simplicity argument, while true for the language proper, is false for advanced usage.

[1] For example, you can't write Ma * Mb, but must remember the dot product method name.

Is it necessarily a good idea to make Go into a number crunching language?
I have no idea, but that is irrelevant. It is an example. I'm sure you can imagine others in the use case you define for the language.
> I wish developers would stop equating "complicated" to things "I don't understand".

Rich Hickey's presentation on this topic should be required viewing for everyone: http://www.infoq.com/presentations/Simple-Made-Easy

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.

This is an inadequate analysis. I am writing a soft real-time system in Go, and GC pause simply isn't an issue for me. Go allows one to greatly limit the reliance on GC. The GC in Go certainly places an ultimate limit to the memory footprint of any one Go process, but a whole lot of productive work can be done within such limits.

Also, my program is a rewrite of one in Clojure, so it ran on the JVM. Go is giving me better performance for my particular application. Also given that Go is just starting out in its development, I expect there to be some improvement in the future.

"But, for systems programming, abstractions suck. [...] But for tools that only need to do one thing and do it extremely well, it's either that or C."

C and Go are just two particular piles of abstractions. The tools work better for some problems, but that's not because "abstractions suck" for those problems.

> 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."

These languages have built-in abstraction tools (templates) that you can use to create your own abstractions. What I like about Go is the abstraction tools are primitive and allow for consistent & precise expression. The expression may not be as concise in certain cases, however, you can build in the mechanisms into your architecture.

> 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 abstraction itself too. (Anyone who has ever seen a gcc compiler error for C++ knows how this feels.)

That is why custom abstractions to your problem domain are important. A framework or a language with lots of features will get you started quickly by providing out-of-the-box tools that you can hang your program architecture on. However, I prefer to have a custom architecture & idioms which are appropriate to the current domain & evolution of the domain.

>What I like about Go is the abstraction tools are primitive and allow for consistent & precise expression.

Well, not really consistent.

For example, try having a range loop for your own structures. Or something like make for them.

And not really precise. The need for interface{} and type switches in idiomatic Go code throws preciseness out of the window.

They claim this is a feature, not a bug. It means that range will never block or do weird stuff. Except of course when it does (bastard question for those who think they know : what does range do on a nil channel ?)

Despite all these clarity claims, go has significant pitfalls, like the nil channel above (and you will enounter nil channels). There's other things, like "what is a pointer in Go", if your answer involves "*", I urge you to reconsider (hint : what's the difference between []int and [5]int ? Is one of them a pointer ? What about channels (of course I talked about nil channels) ? Maps ?

But every type can be typedeffed to a pointer type of itself, like in Pascal (lots of things look like pascal), and result in completely unpredictable reference or value semantics (or my favorite : partial reference semantics).

Does go have generics ? YES (make, range, ...). Go has something no other language has : return type generic function types (meaning a functions meaning changes depending on what you assign the result to, like range). Does Go have operator overloading ? Is Go object oriented ? YES (including single inheritance). YES. Does go have (complicated language feature X) ? Probably yes. But all of these features are only accessible to Rob Pike, who has apparently decided that nobody has any use for any kind of tree or graph data structures, matrices, complex numbers, or so.

In practice you can catch the go team themselves in errors on the language semantics in their presentations, so I think a VERY strong case can be made that it's not at all that obvious.

But the truth is : this language, due to politics (high position of it's inventor) has 10 or so FTE behind it, with lots of paid people contributing various small bits. Is it anything more than some guys idea of his own favorite programming language ?

The honest answer is simply : no.

The only real advantage Go has is a small, yet functional and pretty complete standard library (like C++ had in the 1980s). It is an advantage that will fade, just like it's faded for every other language.

I believe Go is "popular" because of Google, it might solve a particular internal problem for them, but for the rest of the world, they're better languages. To its credit, it makes Java look advanced, and that's no small feat!
> Generics?

Built-in slice and map types cover most real-world needs quite neatly anyway.

For some applications. But once you start venturing into the realms that Rust is targeting, having user defined generic data structures is very important.
> 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."

They also tell me "now you don't have to wait for the language designers or compiler writers in order to 'implement another feature'." Not that _I_ would necessarily be this "brilliant" guy that implements these features. Most likely I will just find some third party library that does it.

The problem is when you have to maintain your code with millions of lines, and hundreds of abstractions.. those "features" will hunt you in your nightmares at night

This is the "The Curse of C++" and some languages pointed in the article while beautiful and correct at first sight are going down in the same road..

Do we use a programming language to look smart, to create correct code or to efficiently solve problems in a maintanable and sane way?

Go is pragmatic.. theres nothing wrong with that.. but i agree that adding some features to it would not hurt either (like generics and enums) :)

To understand a language, one must know what problem it was designed to solve. It isn't always obvious, or what it initially looks like it was designed to solve, or even what the community thinks it was designed to solve.

Erlang, for instance, isn't about concurrency. It's about reliability.

Go, I think, is also not about concurrency. It's about building a language that can be sanely used by reasonably large groups of people of varying levels of skill, yet still produce fairly good software even so, without the language forcing a complexity explosion to deal with it.

Consequently, this does not appeal to a lot of relatively skilled programmers used to programming alone. It isn't my personal pick of favorite language, for instance. However, if I could push a button for free, I would convert my workplace of a couple hundred developers to it in a heartbeat, whereas I probably wouldn't actually do that with my favorite language. It is not, of course, a magical fountain of code quality, but it would give me the best tools and best foundation to clean up code bases that in all the other candidate languages I know are one or another sort of mess.

If I were starting a new startup right now and Go were even remotely appropriate, I'd use it. But in my hobby projects? Not really. Except maybe to smash out a microwebsite, it's pretty good there.

So, you know, a lot of the question is what exactly are you looking for in a language? I like Haskell, but the idea of even proposing to change a project at work to it is laughable... and this is important... nor would I expect to enjoy the result two years later if I won. The mess of code that would result from people hitting Haskell with a stick until it did what they wanted it to do would be an unstoppable torrent of ill-conceived code. On the other hand, Go would almost certainly produce much cleaner code, because that's where it really shines. Maybe it isn't "good", but it's the best choice right now in a lot of places.

(It's interesting to contrast Go's approach to this problem with the other major language to tackle this problem space, Java. Despite attacking the same problem, the approaches are significantly different, and I think Go's way better. I'd hesitate to actively predict this, but Java could definitely be feeling some heat from Go in three to six years in a way that very few languages have actually managed to provide any challenge to Java in a long time.)

I have the same line of thinking as you do.. is the difference in use something you like and can work, and choose something you can use in a bigger group..

HN crowd are top smart.. things like Rust and Haskell are a breeze for people here.. but this is not the reality of the tech field.. the majority of people i know in tech, cant handle more powerful languages.. its too much for them

In the end is just that.. know how to choose the right tool for the job.. and dont do it with your ego..

Adaptation is really important.. and in your thoughts we can see a lot of that..

In Wonderland people may have the IQ to spend for the extra concepts and power a language may provide.. but experience is antagonic to this dream..

The cool thing of smart people to make complex things more simple, is that much more people are able to follow.. is the democratization of computing.. this is in total odds against the elitism we can see in some tech circles.. and im totally against it..

Rust and Haskell are made to reduce the amount of problems you have.

I've seen newbie programmers learn Haskell as a first language in under a term. So I don't believe the marketing from the Google people that their language is worse because it is simpler.

Choosing something because marketing told you it was easier is just as silly as choosing something because of ego. Calling people egoists when they choose a tool because of reasoned arguments based on evidence is simply anti-intellectual, and rude.

"I've seen newbie programmers learn Haskell as a first language in under a term."

Even as a fan of Haskell, I have to say that if you spent one term on Haskell and one term on Go, the latter students are going to be far closer to a place where you could drop them into a real job and get real work out of them. (That said, as I sit here and look back on what "one term" really constitutes, it's not much, regardless of language.)

> Choosing something because marketing told you it was easier is just as silly as choosing something because of ego. Calling people egoists when they choose a tool because of reasoned arguments based on evidence is simply anti-intellectual, and rude.

I Just dont know where in the post i did say something like that.. marketing? egoists? where did i said that?

If you happen to be above average in say english language for instance.. and you know a lot of words and sentences, poems.. but the thing is.. if you choose the speak in the english you know better, you will communicate with fewer people.. or you can just jump to a lower level to communicate so everybody can understand..

Of course you can use the advanced english with elite people.. you can teach people the 'better english' but a lot of them wont care, because they care about other things.. and i dont blame them.. (they may be worried about math or trips)

My point was just something like that.. nothing more, nothing less