Hacker News new | past | comments | ask | show | jobs | submit login
Warming up to Go (jeremymikkola.com)
155 points by scapbi on Sept 12, 2015 | hide | past | favorite | 166 comments



> Whenever I work in Rust, I find myself having a good time mucking around with the abstractions, but not really getting anything done toward the problem I’m trying to solve.

A couple of points:

1. Realistically, this is a programmer problem that no language can solve. If you don't need the abstractions for the code you're writing, don't use them! This is hardly something unique to Rust. A common complaint that experienced Go developers have about new Go programmers is that they overuse channels and goroutines--mechanisms of abstraction (of control flow, broadly construed). But nobody is arguing that Go would be a better language without channels and goroutines. The solution is to train new Go programmers to stop overusing channels and goroutines and to focus on the problem at hand. That's the same advice that I would give to anybody thinking about the perfect generic way to solve their problem in Rust instead of getting the job done.

2. Generics aren't exactly optional in Rust. Let's assume for the sake of argument that generics are a bad feature that languages should not have (something I completely disagree with, but in any case). Even supposing this were true, it's not like Rust would be a better language without them. Rather, the language could not exist, because it would be impossible to satisfy its core goal of zero-overhead, runtimeless memory safety. You couldn't have smart pointers without generics; you couldn't have iterators; you couldn't have Option; you couldn't have any functions that take and return pointers at all because lifetimes are a form of generics. Go is able to get away with not having generics because it leans heavily on having a runtime to support the built-in data structures and on garbage collection to make memory management decisions at runtime. Not so with Rust, which is designed to bake all that information into the compiler to avoid the overhead of a runtime and of garbage collection.


> Realistically, this is a programmer problem that no language can solve.

> The solution is to train new [language X] programmers

There is high demand for software that blocks "procrastination" websites, because people struggle to focus when the whole internet's around. I know someone who's hidden their computer charger in order to stay focused. I've read a surprising number of blogs about aiding writer's block with minimalist text editors. I even know someone who needs to offload finances to another party lest they spent it too quickly themselves.

You can tell people they just need training, but if the whole standard library screams "abstraction!", people are going to abstract. A language like Go is the minimalist text editor of the programming world. Sure, some people will scream they need their MS Word macros and floating panes (and it's even more true in CS), but these tools are an effective treatment of the symptom nonetheless.

> Generics aren't exactly optional in Rust.

Sure, but this doesn't change the trade-off. One should use Rust where the problems it solves best broadly match with the problems you're dealing with. And, IMHO, Rust is an exceptionally high complexity language for small projects.


> You can tell people they just need training, but if the whole standard library screams "abstraction!", people are going to abstract. A language like Go is the minimalist text editor of the programming world.

Should goroutines and channels be removed from Go then, because new programmers abuse them?

I think the right way to look at programming language features is to look at the cost-benefit tradeoff. Generics, to me, are clearly on one side of the line.

> And, IMHO, Rust is an exceptionally high complexity language for small projects.

I don't think the complexity of Rust is that high, and "small projects" versus "large projects" is the wrong way to look at the value of abstraction anyway. Ruby on Rails is overwhelmingly used for small projects precisely because the abstraction/metaprogramming features of Ruby help small projects so much.


Go's "channels" do a lot more sharing than the Go sales materials claim. If you send a slice across a channel, you're now sharing unlocked data across a concurrency boundary.

Go needs a borrow checker and a more Rust-like ownership model. Then you could safely use channels a lot without risk of somebody breaking an implicit assumption about concurrency safety.


> I think the right way to look at programming language features is to look at the cost-benefit tradeoff. Generics, to me, are clearly on one side of the line.

The right way is to look at the cost-benefit tradeoff in context. There is no fixed line. Go has goroutines and channels because the designers judge that for the area they're aiming at, the benefit outweighs the cost.

Same with generics - don't conflate them being good for you in your circumstance to them being good for everyone in every circumstance. We can argue to the ends of the earth where that line lies, but the fact is that some people in enjoy the simplicity that comes from Go not having them.

(You could argue this is just another Blub paradox, but IMHO that would only make sense if the designers weren't proficient in languages including generics, like Java.)


>, but the fact is that some people in enjoy the simplicity that comes from Go not having them.

The Golang team was not extolling the lack of generics in Go as a point of pride. They have admitted that it is a useful feature but they haven't decided yet how to add it to the language. (Maybe they never will.)

As far as it being "simpler" that Go doesn't have generics, I've never understood this conclusion. If you don't have generics as a 1st-class language feature, you've now forced complexity into the users' code with empty interface{} and copy & paste code. Or, you create new complexity by writing a code gen tool to simulate generics. The complexity is now in the code-to-write-code-tool & the build process.

Certainly, there are some languages that don't need generics: proprietary 4GL such as SAP ABAP, procedural-SQL such as Microsoft Transact-SQL and Oracle PL/SQL, and shell scripting languages such as bash.

However, with Go positioning itself as a "general" programming language, the lack of generics is more a consequence of Google's priorities and timeline of development rather than a pinnacle of design. Programmers have wanted to write algorithms that work with multiple types for decades before Go was invented and leaving it out as a language feature actually doesn't make things simpler if "simplicity" is looked at holistically.


> The Golang team was not extolling the lack of generics in Go as a point of pride.

Indeed, they're extolling the simplicity one gets. To quote their FAQ:

> Why does Go not have generic types?

> Generics are convenient but they come at a cost in complexity in the type system and run-time. We haven't yet found a design that gives value proportionate to the complexity, although we continue to think about it.

If they can find a way to add it that doesn't take away the simplicity they enjoy, all the better.

> As far as it being "simpler" that Go doesn't have generics, I've never understood this conclusion.

That's if you require generic objects. There are reasonable ways to write code that largely avoids them (namely, by specializing your code on specific, standard types for the given problem). Whether this is reasonable for your circumstance depends on your circumstance and the way you like to solve problems.


>Indeed, they're extolling the _simplicity_ one gets. To quote their FAQ: [...] If they can find a way to add it that doesn't take away the _simplicity_

You're missing my point because you've used "simplicity" twice in a way that I was trying to define in a more holistic manner.

You, and the FAQ you quoted, was talking about language-specification-simplicity. I was trying to make the discussion more cognizant of holistic-simplicity which means new complexity is created when one part is "simpler".

>There are reasonable ways to write code that largely avoids them

There are workarounds for everything, sure. Workarounds affect holistic-simplicty. Also, what's "reasonable" is subjective. In any case, I'm not convinced that language-spec-simplicity trumps holistic-simplicity.


It's just a different way of solving a problem. Labelling it a workaround is disingenuous if people are claiming they prefer writing code in that manner.

It's also incorrect to dismiss language complexity as specification complexity. One might just dismiss all of C++'s complexity as specification complexity. It ignores all the difficulties it adds to run-time, compilation (speed, correctness and compiler flexibility), incidental knock-on effects on other language constructs, external tools, syntactic simplicity, learning speed, code consistency and, as the article covers, just being itself a distraction from the real problem.

And if you write code in this manner anyway, the simplicity in the language makes a big difference.

> Also, what's "reasonable" is subjective.

I fully agree.


> Labelling it a workaround is disingenuous if people are claiming they prefer writing code in that manner.

Well, I've never seen a Go expert espouse this opinion. Do you have a web article that actually says that? What I see is that many experts just carry on being productive in Go in spite of not having generics but that's a different position than actually preferring to copy&paste code or add a preproc code-gen (as Rob Pike once suggested as a workaround).

>And if you write code in this manner anyway, the simplicity in the language makes a big difference.

One can write in "that manner" whether generics was there or not. Existence of generics doesn't take away the ability to continue with suboptimal techniques such as copy & paste of redundant code for different data types.


> Do you have a web article that actually says that?

That says what? That they don't find not writing generics problematic? Rob Pike has said so himself. The article you've just read says exactly this[1].

[1] "With no generics, you are discouraged from making big, overly-general abstractions. This might be on purpose."

> One can write in "that manner" whether generics was there or not.

I have a feeling we're talking past each other. I'm saying that if you're not going to use generics, there's no point paying the costs of having generics. Not paying the cost means you get a simpler language, which has run-on benefits I mentioned before.

I'm not saying that generics are bad, or that emulating generics with copy & paste is better than generics.


Go has generics. Channels and maps are generics. You just can't define any new ones. It should at least be possible to define things that work like channels and maps. Those are more like parameterized types than generics, and going all the way to generics may not be necessary.


> Same with generics - don't conflate them being good for you in your circumstance to them being good for everyone in every circumstance. We can argue to the ends of the earth where that line lies, but the fact is that some people in enjoy the simplicity that comes from Go not having them.

You can find some person who enjoys anything. But I think having basic OCaml-style generics has essentially zero downsides and so many upsides that it's essentially an open and shut judgment call for every statically typed language.


When it comes down to it, everyone seems to have their pet abstraction. Everyone except the old grumpy programmers. And YAGNI always applies, to some degree, because you can always build back up to some approximation of the "gotta have" abstraction from cruder primitives.

So the benefit of the language design is to make it so that the abstractions you _do_ use play well with everyone's code instead of being a ball of duct-tape and easily broken conventions.

I think that is missed when people go looking for a language with power tools. You can already do things too powerful to comprehend in C if you add enough macros and pointer indirection. But if you sit down trying to solve a problem using fewer language features instead of more, you usually come out ahead - because then you're forced back towards first principles about what the project needs and see the abstractions in the proper light. Powerful languages lead to an ecosystem of correspondingly powerful libraries, which drags you into the mire no matter what.

While both Rust and Go are at a lifecycle stage where they're dogged by language window-shoppers, Go is actively resisting the people who didn't intend to make a purchase, leading to "Go not powerful" whinge blogs. We end up knowing less about Rust's productivity because most of the early adopters are going to be the kinds of people who seek innovative ways to break compilers - demanding customers, who don't give much back.

That doesn't mean that Rust is actually unproductive, as it's quite likely that if you try to write C-style code with few ornaments, you will have little difficulty doing so, and the technology will mostly break in your favor. But it does mean that it'll take longer to work up momentum for a production-ready ecosystem, since the conversation will continue to revolve around "ooh, we've never done that before's" with borrow checking and metaprogramming. And when it's all over, it's likely that simpler ways to do these things will come to light - either in Rust or in another language.


Many of the abstractions Rust has have their nuances hashed out at compile-time rather than run-time, and that's the big benefit.

Go programming eventually forces you to do primitive stuff like stuff print statements into your code and tinker with a compile-run-compile-run cycle that ends up wasting quite a bit of time.

For something like microservices it's actually quite good because if you keep your scope as small as possible, you end up with dead-simple code for dead-simple tasks ... until you need a way to reign in all your microservices, where it starts to fall apart because you've got unavoidable complexity and a tool that isn't designed to handle complex things. Tools like Rust start to shine in these situations.


> And YAGNI always applies, to some degree, because you can always build back up to some approximation of the "gotta have" abstraction from cruder primitives.

What I was saying with point (2) is that this is explicitly not true for Rust.


Yes and no - it's a tradeoff abstraction. The borrow checker is a whole-language design directive, and its strength comes from giving up something in other areas - in this case, having to use generics everywhere. A language, as I said, is mostly made up of these intentional tradeoffs to try to find an appropriate balance.

You can still express the abstractions of Rust through another language, though it may take writing another compiler on top to actually do so - they just won't hold the same utility, because the utility of this abstraction is mainly tied to the "zero-cost" implementation method, not to whether it has expressive powers.


I would go so far as to say that any static language without some form of generics will eventually get them. Even in C, the preprocessor is often (ab)used for this use.


And C11 has _Generic for macros (though it's rather limited and really more just like overloading than generics).


> If you don't need the abstractions for the code you're writing, don't use them!

So much this. Better to have the tools for when they're needed than to swing a hammer at everything regardless of whether it's a nail.


>For someone new to the codebase who knows nothing of the giant abstraction, which do you think is easier to grasp: 10 lines of code that only make sense if you know the abstraction, or 20 or 100 lines of code that can be understood on their own?

I'll take those 10 lines of code.

We've only managed to build powerful systems because of ever increasing abstractions.

See Alan Kay's DSL research for example: http://www.vpri.org/pdf/tr2007008_steps.pdf

It's not like every system with abstractions has to be done the Java way. Functional programming, be it Haskell or Lisp etc, has tons of abstrations too, and they don't get in the way.

I'd say Java's issue wasn't abstractions per se, but uneeded abstractions, BS boilerplate and needless ceremony. Which Golang also suffers from.

Besides, you don't just dispose of the abstractions and get the same, but now straightforward, code. You get rid of them and get 10x the code (the 100 lines compared to 10 he mentions), which you have to keep track of and keep in your head, only now you don't have high level abstractions to help you in that, you have to do it manually.

(And of course Go vs Python is not a good comparison, as Python's lack of types make it difficult to reason about large programs quickly. How about Go vs C#/Swift/D etc? I'd take Swift any day of the week.


There are always trade-offs.

For example, I'd rather read explicit 100 lines of explicit SQL than the 10 liner Active Record abstractions I've had to come back to on my old Rails projects.

It turns out that the 10 liner forces you to keep much more in your head (the whole Active Record abstraction, its idiosyncrasies, its edge cases, its performance profile) than the 100 lines of explicit SQL that let you bootstrap a mental model without having to credentialize in documentation every time you touch the project.

I'd also rather read 100 lines of explicit authentication middleware than 10 lines of Devise configuration (a Rails authentication gem) for the same reason.

The appeal of "keeping less in my head", as we put it, is one of the main reasons I use small frameworks these days.


> And of course Go vs Python is not a good comparison, as Python's lack of types

Nitpicking: Python has types, they're just dynamically determined instead of statically. That said...

> make it difficult to reason about large programs quickly

... I agree 100%. At $DAY_JOB we have a very large Python codebase and it can be extremely hard to understand what's going on, let alone refactor safely. I really miss static typing.


But the expense of extra lines of coke come at significant maintenance costs. It means needing more test and the inevitable bugs that will be discovered.

More lines of code is inherently a bad thing, while I'm all for simplifying the code so that people can understand it, I'm not a fan of doing that at the cost of increasing the amount of maintenance that will be needed on the code base overtime.


> But the expense of extra lines of coke come at significant maintenance costs.

That is the single greatest typo I have ever read.


Also in the running, this priceless slip: https://news.ycombinator.com/item?id=3726842


Looks like that one was later corrected. Wondering what the mistake was originally.


still there - the inadvertent your mom joke in last sentence with "uses" instead of "users"


So does your mom.

Pretty sure this will get flagged, and I hate myself for it, but it will make sense when you review that post again.


Code is coke at least this is true for some hardcore people here.


And its wonderful to see. I'm humbled a̶l̶m̶o̶s̶t̶ every time I read HN.


> But the expense of extra lines of co[d]e come at significant maintenance costs.

All lines are not created equal.

    if err != nil {
        return err
    }
Those 3 lines have very, very little maintenance cost.

The obvious must also be mentioned: less lines of code usually mean building on abstractions - the lines of code are in there, somewhere, but not in your code. Better hope that abstraction is well-tested. Of course it's all a matter of balance, but I disagree with "more lines of code is inherently a bad thing", which stated another way, would mean "cram as much information as possible in a single line of code".

In the end, lines of code are a bad metric of software.


Less than other lines, but I definitely would disagree with the doubled-up "very" on "little". In addition to simply being distracting and verbose, a core issue is that you have to make certain you always have the boilerplate and that you have to make certain it is always correct. Otherwise you just https://gotofail.com/ :(.


Those are some truly bullshit lines there.

Totally unnecessary, a solved problem eons ago through very manaeable, existing constructs that have a lot more semantic meaning.


> Those 3 lines have very, very little maintenance cost.

These three lines are everywhere, give me no clear indication of whether they are intentional behaviour or merely a boilerplate-by-convention, and worst of all, they obscure the actual business logic.


Error handling does not obscure the business logic, it is very much part of the business logic.


Error handling does obscure the business logic when, as in Go, nearly every operation needs to be followed by a boilerplate guard clause.

Other languages (e.g. Java) have solved this problem quite elegantly over a decade ago with the introduction of Checked Exceptions[1].

Since this is the pattern that every Go program ends up emulating anyway, Google could save everyone a lot of work by just baking it into the language.

In the eternal words of Larry Wall:

  The computer should be doing the hard work.
  That's what it's paid to do, after all.
[1] https://en.wikipedia.org/wiki/Exception_handling#Checked_exc...


I think almost 50% of my Go code is checking for errors. Every time I see:

foo, err := somebullshit()

if err != nil {

  // bleargh
}

I feel as though I've experienced a very slight but irrevocable grand mal seizure.


Sorry, but if you have every operation followed by a guard clause you are doing it wrong.

For example,

https://blog.golang.org/errors-are-values


That's the right approach, but it's still decades behind what a decent programming language would give you for free. You have to copy-paste that "errWriter" definition for every possibly-error-raising method that you want to call, because go has no generics. If you want to write to two different writers, let alone a list of them, then you have to go back to the function-and-variable version, which is very boilerplatey and impossible to reason about when refactoring.


This just shows that, to avoid having do-and-check after every operation, the provider needs to invent several idioms because it's tedious to the consumer.

But now the consumer needs to know multiple idioms and recognise when they are being used. And the reader of the consumer code needs to know that the normal `err != nil` idiom is not being followed. All of which requires everyone involved to do more reading of source code than a "throws" line.

How is this simpler than having a single, universal concept of exception handling?


Your link describes (and encourages) exactly the laborious emulation of Checked Exceptions that I was talking about in my above comment.

It doesn't make the guard clauses disappear. It merely forces the programmer to manually aggregate them at a higher level with even more boilerplate code.


If my guard clause is doing the same thing for 20 lines, it increases readability. Could be an optimization point too!


> https://blog.golang.org/errors-are-values

The "helper" function there introduces 10 lines of code (per function in which you want it), and doesn't even work if you call anything besides that one `write` in sequence.


Mostly agree, but I'm sure you must have meant UN-checked exceptions :-)

  if ( somethingICanDoNothingAbout)
      { throw new UncheckedGameOverException( "You're screwed!") }
...

  // top level event/request/message handler, many layers up
  catch ( Throwable e)
      { log.error( "Game over:", e); ... }

(I'm with the Anders/C# camp on this one, as much as I dislike MS otherwise)


No, I mean Checked Exceptions. Unchecked Exceptions are the devil and should be abolished.

The inclusion of Unchecked Exceptions is one of the biggest warts on the Java language.


Unchecked exceptions are for programmer errors. They are an important part of the language since humans (who write the programs) are error prone. They are unrecoverable by nature. Checked exceptions should only exist in situations where the caller can definitely recover from them. Arguably almost nobody does the latter correctly when choosing checked exceptions and the consensus is, as a result, this is the kind of exception that should have been omitted from the language.


Unchecked exceptions are for programmer errors. They are an important part of the language since humans (who write the programs) are error prone.

I should clarify: The wart is that programmers can introduce their own unchecked Exceptions. This should not be allowed since, as you say, unchecked Exceptions only make sense for unrecoverable runtime errors.

Checked exceptions should only exist in situations where the caller can definitely recover from them.

False dichotomy. Unchecked exceptions become part of the callee's method signature, i.e. the contract that the caller must fulfill.

Arguably almost nobody does the latter correctly when choosing checked exceptions

Baseless claim. I see many people using Exceptions correctly and elegantly, for error handling and flow control.

and the consensus is

Opinion != consensus.


There are times when Go (et al) style multiple return values would better serve a function than exceptions.

For example, a function to parse a string into a number should return both a number-reference, and a flag of some kind (or maybe just an Option). Crap input should not generate an exception like out-of-memory or an I/O error. Trying to dereference the number w/out checking to see if the string was parseable COULD generate a not-mandatory-to-check exception, though.

I guess there is a certain amount of tension between the idea that functions/methods should document the kind of errors/exceptions they might have, and between the annoyance that is all the crappy do-nothing catch clauses that turn around and re-vomit wrapper exceptions that ultimately land in a "well, it didn't work" log message and punt, rather than really "handling" the original exception in any way, shape or form.


> Error handling does not obscure the business logic, it is very much part of the business logic.

That's why it's nice when the language provides tools to make it more likely to be correct.


If your business is handling full disks and HTTP timeouts, sure, exceptions and business logic are the same.


The further away from a "network server" you get, the less appealing Go gets. It's a general purpose language, but it's not entirely wrong to think of it as a DSL for writing network servers that happens to be useful for other some things.

Why doesn't lack of generics kill Go? Well, if you're working in Go's wheelhouse, []byte actually covers a lot of things and you're rarely that far away from it, because you're about to read or write a []byte real soon now. (I don't mean you always have one literally in hand, you probably marshaled in into a struct, just that you're often very close to either reading or writing it.)

Why does it work to have this error handling in Go? Well, when writing network servers or other basic cloud infrastructure, actually a lot of the errors require different handling on a case-by-case basis. Big ol' exception blocks around the whole operation are often hiding bugs, or at the very least, suboptimal exception handling. By contrast, when you're not writing the code to do all the nitty-gritty network operations it's a level of detail you don't want or need.

I think every single posted "success story" I've seen for Go has been a network server. I don't think I've ever seen one about how it really cleaned up my Android app, or how my game development was crashing and burning until I switched it to Go. I don't think this is a coincidence, nor do I expect to see one anytime soon, the recent Android support notwithstanding.


The most common maintenance task is reading an entire region of code, which costs in direct proportion to the number of lines. Bugs are also proportional to number of lines of code independently of language, http://programmers.stackexchange.com/a/185684 . So no, lines of code is a good metric.

(Though I wouldn't characterize it as "cramming"; better languages save lines by not requiring programmers to specify irrelevant detail rather than by packing more information into the same space).


I routinely find bugs in golang error handling code. In general I agree with your point but this is not the example I would use.


As opposed to the bugs you didn't find in some other language, because your program was spitting out unhandled exception errors from random places at runtime.


An unhandled exception means the program stops rather than continue in an unexpected and possibly invalid state. The latter being what happens in C or Go.

And it's not like these two are the only possible error handling strategies.


That isn't really what happens most often. What happens is that the exception is caught and thrown out, or caught and printed (especially with java if you are using eclipse_.


Why would I spit unhandled exception errors from a Try monad?


These three lines have made my life easier.


... and your code more fragile.

You can't have your lunch and eat it too: if you want robust code, you -- the developer -- need to spend some time thinking about how you manage your errors. Go makes it all too easy to sweep those under the rug.


No it doesn't. You need to handle every error or your program will panic and crash. You are forced to think of and handle every error by the if err != nil construct.


The problem with verbose code, is missing the small difference in the boilerplate and creating new bugs.

It's the same problem with copy/paste code. With copy/paste code, it often has small changes that you can miss easily. You have to read every line of code to not miss the small variations. If the person bothered to generalize the copy paste code a bit, you can cover all cases in one spot, and be less likely to miss problems.


Seems like there should be an abstraction for that.


Go is a little more verbose in basically two ways: it forces you to handle error states explicitly, and it doesn't let you do overly-clever things to save a few lines of typing. Neither of which increase the maintenance or testing burden.


Programming consists entirely of doing overly-clever things to save a few lines of typing. Go belongs to the same school of ludditery as von Neumann's opposition to assembly.


> it forces you to handle error states explicitly,

Does it?

`if err != nil` is a convention. It's not a checked error. It's like saying "C has explicit array bounds checking", if the convention was to check it by hand.

For all the hate Java gets, when I have a checked exception, I know what it is going to be. I don't need to dig around in sourcecode trying to work out if I should care or not. I don't need to check which of the three Golang error-distinction idioms (package-variable errors, error subtypes, plain-old-errors-with-magical-strings) is in play.

As you can guess, I don't like Go.


> `if err != nil` is a convention. It's not a checked error.

It's... obviously both.


Sorry, typo. "Checked exception", meaning that the compiler enforces error handling and that you can look a method signature and learn what kind of exceptions the method can throw without needing to read its internals.


'err' is sitting there in the return value and the compiler will force you to either use it or explicitly steamroll it. The difference between C bounds checking and Go error checking is that you can't forget to check an error in Go.


I guess everyone is remembering to check the return values of fmt.Println() then huh?


And runtime exceptions? What about them?


They leave you in about the same position as a programmer in a dynamic language.

The advantage of runtime exceptions over Go is that I will get a fatal crash and a stacktrace. I don't have computation proceeding in an undefined, difficult-to-debug way because I forgot to do `err != nil` somewhere (particularly for functions that only return err).


Or someone will catch it and discard the result which is often often what occurs. For example, with java throw an exception in an executor task (an assertion failure for example). What do you think occurs?


Which happens in Go too. I've seen a lot of code that happily sails past functions which only return err, meaning the compiler never pipes up.

Don't get me wrong, though. I become grumpy when I see empty catch clauses. Or a logger figleaf.


>doesn't let you do overly-clever things to save a few lines of typing

So that's what we're calling the lack of HoFs these days? It's really sad to hear that anyone considers map/fold/filter are "overly-clever".

Tim Sweeney (Epic) says[1]:

"“For” loops in Unreal:

40% are functional comprehensions

50% are functional folds"

I hardly see how changing those loops into a construct that more accurately represents them is overly-clever.

1: http://www.cs.princeton.edu/~dpw/popl/06/Tim-POPL.ppt


> So that's what we're calling the lack of HoFs these days?

Go has HoFs. The lack of parametric polymorphism is what prevents you from writing `map` in Go (subject to several caveats surrounding efficiency and type safety).


In practise I don't find passing function parameters without generics to be very useful.


> Go is a little more verbose in basically two ways: it forces you to handle error states explicitly

No, it doesn't (simple example: the return value of os.Setenv). errcheck exists for a reason.


If you need more tests to cover the "extra lines of code" resulting from the explicit nature if Go code, then your prior tests didn't sufficiently cover all the edge cases. So Go's verbosity is a win.


> But the expense of extra lines of coke

Please tell me that was autocorrect and not muscle memory


Yikes! Understanding the code is the key to efficient maintenance.


I wonder if Go is taking off due to folks coming from Python and Ruby, looking for something that has better perf and concurrency. Sorta like how Python got a lot of Java users that were just sick of the boilerplate.

The fact he compares Rust and Go is bizarre - they're totally different types of environments. Why not toss in OCaml and Java, too? Go is a managed runtime language and "systems" is only mentioned due to misleading marketing when the language launched. And of course Rust might feel a bit harder - no runtime means you gotta do all that stuff at compile time. Kills the chance for some shortcuts!

People go on about how Go is practical and you don't need features, really. But that's a lame excuse. Facebook was built on PHP. Transport Tycoon was written in x86 assembly. Harry Potter was written with pen and paper. So you don't need.

The attitude from the Go community is almost like an anti-intellectual one isn't it? F#'s inventor said it was hard getting generics into .NET because Microsoft viewed it as "academic and experimental" despite it being old ideas put into practice. Yet another decade later and Go is further promoting this anti-progress attitude.

(I'm not really against Go, just disappointed in how weak available language technology is.)


> folks coming from Python and Ruby, looking for something that has better perf and concurrency

Long-time Ruby developer here, relunctant Go developer. Absolutely.

I want performance, concurrency, efficient memory usage, and static typing. Ruby is lacking in all these areas. Go is mildly better, but is regressive in many other respects.

For me, Go is a step back in terms of productivity, but a step forward in creating stable, fault-tolerant code. It's a compromise. Go feels like a stopgap solution until something better comes along.

For example, I've learned to design the internals of my code around a functional style that makes code composable and the data flow simple. Go doesn't support that style, not really. Go promotes simplicity, but a lot of stuff that should be simple in Go isn't "simple". Like mapping and reducing an array.

What's interesting is that working in Go reminds me a lot of using Borland's ObjectPascal back in the mid-1990s: A small, natively compiled (and super fast to compile), not-functional language with built-in reflection and a stupidly strict, cumbersome type system. Go makes very few innovations over ObjectPascal, and inherits many of its problems; Go's notorious lack of generics, for example, caused exactly the same issues back then. And nobody should be surprised about that.

Currently, the language that looks closest to my ideal is Nimrod. Unfortuantely, it has almost zero mind share, and has a surprising amount of odd, off-putting warts for such a young language (perhaps not too surprising when you realize that for many years it was just one guy hacking away at it, TempleOS-style), disappointing since many design choices just feel just right, like someone has read my mind. Currently I'm on the fence with Nimrod. Jonathan Blow's jai also looks promising.


Since you mentioned having experience with Ruby, have you looked at Crystal as a statically-typed alternative with a deliberate Ruby influence? From what I've seen of it so far it may fit your purpose precisely.


Crystal does indeed look interesting and promising. But it will be a few years until I'm willing to consider it for production code, I think.


>I've learned to design the internals of my code around a functional style that makes code composable and the data flow simple.

Curious: why not go all the way and use Scala, F#, Clojure, maybe even Haskell?


I've considered most of them, actually.

One consistent, niggling problem with all of these languages is that they are still obscure compared to everything else, with all that this entails: It's harder to find developers who already know the languages well enough to be true masters, let alone find juniors who are interested and capable of picking them up; the languages are often complicated and unfriendly, so junior developers will again struggle.

Something I've learned the last few years is that while having only wizards on your team is great, it's useful to have access to a less advanced contingent of developers who can still contribute productively.

In the end, if you buy into something that not every shop uses, there will be a lot of sunk cost into having brought everyone into the fold, and you end up being an "OCaml shop" or "Haskell shop" or whatever. Productivity will probably normalize, but I'm not sure I want to go down that route.

OCaml looks to me like the most promising functional language with the fewest warts, the most pragmatic approach to real-world apps, and possibly the best performance. But it's not a simple language, with a learning curve that's similar to Haskell. I also have to say that like Erlang, the toolset and syntax occasionally feels antiquated (for example, there's no excuse not to provide a modern readline-enabled REPL). OCaml still is not good at concurrency.

Scala has some good parts, but is overdesigned and way too complex, in my opinion. Last I checked, performance was worse Java. It also runs on the JVM, which means its memory usage is inherently inefficient.

F# is a language that I like a lot; in some ways it looks like a more pragmatic Haskell, or Ocaml with a cleaner syntax. I don't like the fact that it's tied to .NET, which (like Java) means it inherits all the issues from that platform. The performance on Mono looks unsatisfying.

Haskell is also a language I like a lot — at least in theory. I've been disappointed with performance and memory usage (and the ability to reason about those things). The tools for concurrency (and OTP-type distributed networking) also seem very much lacking. Whenever I see an example of parallel async code in Haskell, I feel like I'm reading an advanced physics paper. While I like and understand the approach to purity, I feel like the tools get in the way of programming; that I have to work at getting in and out of effectful code just so that I can stay pure, and for what reason? Bugs caused by side effects is something I encounter only a few times a year, if that; Haskell builds a huge wall to solve something that isn't a problem for me. Nor do I enjoy that laziness is the default. Finally, there are parts of the language that seem immature; the well-known data record namespacing issue (being fixed?), and the sheer awkwardness that comes with trying to implement certain things (like polymorphic collections and directed graphs) that should be simple, but aren't. Still, it's a beautiful language, but not one I would use for everything.

Clojure is the JVM, again. It's also not statically typed. From everything I've seen, performance is worse than Java.


Using a language that isn't already in every shop may limit hiring at first glance, but it will automatically select for folks who have an important sense of craft for the work they do and pay close attention to where there is a lack of return on investment in languages, platforms and applications. People who really care about FP will likely be easier to retain than those who use Blub and couldn't care less. Go is shiny right now. What will be shiny later? Lisp is not shiny but it is power like power has never known with Blub.

Aside: I've not found Clojure to be slower than Java and I've benched it. It is fast, fast, fast. As fast as Go even doing the concurrent thing: benched that too. I've even found that Clojure can edge out Java in many cases because of its inherent laziness: the fastest way to expedite a job is not to have to do it in the first place.


With OCaml, that depends what you mean by concurrency. There's good async IO, but the runtime is still single threaded (just like Node!). The overall library ecosystem is a disappointing though.

https://github.com/janestreet/async

And it is a huge shame F# doesn't work well cross platform. Hopefully we'll see the runtime ported soon. Looks like it builds, but the tests are probably a mess:

https://github.com/dotnet/corefx

Haskell, yeah, very much agreed.

Hopefully one day a big company will build us a modern language with a vibrant ecosystem. Microsoft has come the closest with F#. Rust is tentatively exciting, but who knows if Mozilla has the muscle to get it adopted.


Yes, I was referring to OCaml's single-core limitation. I was under the impression that some smart people working right now on removing the global lock, though?


> The attitude from the Go community is almost like an anti-intellectual one isn't it?

I hear you. There was a funny post a couple years ago weighing a bunch of languages on republican/conservative vs. democrat/liberal scale. But the author erred with Go. Go is certainly the most republican conservative language out there today.

Go not only adopts the anti-intellectual stance but comes across as a language that's controlled by "religious leaders" who dictate not only the the direction of the language but its "morality" too. And those who use the language feel the same hidden pressures from constraints. This leads to a weird state of having to "believe" in a language and defending things: "like less is more", "choices are bad", and having others determining what you want and need.

And this is the most annoying part. Because Go is really not a bad language. It's simple and constrained but easy to get into and easy to get stuff done. But Go's the whole attitude comes across as somewhat off-putting to window shoppers who might actually purchase and be fine with it otherwise.


Anti-intellectual seems an off key remark to me, I'd say Go just takes a different school of thought. Intellectualism doesn't mean everything should follow one path.


I don't think Go is anti-intellect as a language. I'm saying it's feature set makes it a definite choice to track an austere path. This seems to bring about a certain zeal and, especially for recent converts, a seeming need to proselytize. It's as if those new to Go need to convince themselves by saying out loud that they are, in fact, heading down a good and righteous road. I just haven't seen this sort of quasi faith-based argument coming from the other languages in the Algol tree.


> That said, for some projects I’d prefer Rust. Specifically for very large projects, a heavier reliance on abstractions starts to make more sense.

This is an interesting point, because I've never actually worked on a large project that shipped. Lots of brow furrowing and hand wringing over correctness, but at the end of the day nothing for customers to use, or too late for it to matter - which is more or less the same thing.

Conversely, I've seen plenty of small "shim" projects that grew, sometimes rapidly, into much larger projects. Few, or no tests. Awkward, meandering blocks of logic, often with developers lamenting the lack of good abstractions (myself included).

Large projects tend to be second systems, and second systems tend to suffer second system effects.

It seems as though the profession of software development curses its practitioners to remain unsatisfiable.


I've worked on many (if not all) parts of systems that are over 1,000,000 SLOC written in languages like Java and C++. I always felt that if I'd used a more powerful language those systems would be expressible at a 1/5 to 1/10 the SLOC.

Once I discovered Lisp, I found that more powerful language. In my experience (I'm now a full time Golang developer) for the things that Go does well, Clojure creates much smaller amounts of code and performs at least as well. When I program in Go, I revisit my younger self again feeling the same frustrations. I don't see Go as The Way Forward except for small "infrastructure" programs in the M2M space.


I've never been a Go evangelist. For years, the appeal and praise lavished upon Go flummoxed me. Until I decided to use Go for my latest product. Then it made sense. For most purposes Go is "good enough." And it's good enough in enough areas for me to set Go as the standard, de facto language at my company, unless there's a damn good reason to use another language.

I appreciate Go because it's fast, small (re: syntax and standard library), garbage collected with nice primitives (pointers and slices), concurrent with high-level primitives (goroutines, channels, select), expressive enough (strings, maps, range, first-class functions, type system, etc.), first-class support for Unicode, and it has a rock-solid standard library.

About a month ago, I started learning Go. It's so small. The language, the standard library, the tools. They're all so easily digestible; it took me 4 days to work through the Go playground, read the entire Go specification, work through and learn 40% of the standard library, and start writing production-ready programs. C was the last language that enjoyed a language/standard library this small, to me.

The same thing can't be said for Ruby or PHP, or any other languages I've worked with. In almost every project I've been involved with, there would be usage of some arcane corner of the language. Whether it's determining the byte offset of a member function in C++, or the GCC extensions to structure initialization in C, or metaprogramming magic in Ruby. All these cracks and crevices are difficult to keep in your head. This isn't so with Go. It's easy to keep the entire language and standard library in your head; providing a certain ease and flow when building a program.

Ease of use applies to memory management as well. Go offers just enough control over memory to make it pleasant to implement and use real data structures. Try implementing an LRU cache in Ruby/Python that evicts based upon an object-size policy without wasting a ton of memory simply maintaining a linked list. When I built a distributed real-time, type-ahead service in Ruby I was fighting the language the whole way when it came to simple data structures, memory management, and concurrency. (It was just a prototype, I know Ruby wasn't the right tool.) Originally, I was going to rebuild the service in C++, but that would've taken too much time. Instead, I opted for Go and it was much easier, and obviously much more performant. Not as performant as an equivalent C++ service, but performant enough.

I don't think much needs to be said about Go's concurrency. First-class primitives such as goroutines, channels, and selects along with solid standard library support in sync and sync/atomic, makes for a powerful combination. Go wraps these concepts in a very bland, mainstream way. Making them more accessible to more people with little time investment.

You're right, Go will not win based upon the # of LOC written. However, after writing 20k+ LOC in Go over the past month, I don't see any reason to write any of our back-end code (infrastructure, services, web servers, tcp servers, etc.) in anything other than Go. It's good enough in enough areas to make it much faster for a team to standardize on just this one language. Not only does this reduce the time wasted due to context switching between languages in a polyglot shop, it makes it a ton easier for anyone to jump in and contribute anywhere at anytime.

---

To give my thoughts some context, here's a subset of my professional and personal language/project history:

- Ruby: 100k+ LOC Rails monstrosities and smaller 20k LOC projects

- PHP: 200k+ LOC bare-PHP e-commerce websites and 50k LOC CodeIgniter/Laravel intranet apps

- Python: 10-20k LOC utility and server management scripts

- C++: 500k+ LOC open-source 3D game engine and multiple <10k LOC personal projects

- JavaScript: ~35k+ LOC for the front-end work on some websites I've worked on

- Common Lisp: read all of Practical Common Lisp and ANSI Common Lisp, and dabbled in a few small programs


Well I'm not surprised you can't see any reason to write anything other than Go when you've never used a language with a decent type system! If you're ever standardizing the language for a whole team I hope you'll take the time to evaluate a decently typed language first - OCaml or Haskell or F# or Scala (or I guess Rust if you absolutely need to avoid GC)


I wish I had the time to evaluate everything that is out there but I don't. There's simply more important things to focus on for my company than choice of language: sales, hiring, fundraising, etc. Therefore, I had to choose the best option for me with the limited information I had at the time. But that doesn't mean I can't be convinced to adopt another language. :) If someone ever surfaces a highly persuasive point-by-point argument for OCaml or Haskell or F# or Scala that applies to what we're doing, then I'll reconsider. So far, I haven't seen such an argument.


You should of course use what language works for you. That said, the series on moving 0install from python to ocaml might be the kind of poin-by-point argument you're after?:

http://roscidus.com/blog/blog/2014/02/13/ocaml-what-you-gain...


For me a big thing is avoiding error prone inconsistencies and other footguns. The less footguns a language has, the better.

For example in objective-c, most of the language deals with nil items without crashing or errors, except for a few APIs and collections. This abstraction mismatch in collections causes runtime crashes and is a language footgun.

Swift fixes this footgun with optional types, but objective-c could of made it's collections nil-safe and apple could of just declared in the company that your apis have to be able to handle nil arguments, no exceptions. The new nullable annotations are an ugly patch to just help transition to swift and still do not fix the footgun.

Java's NPE is another example of a footgun, and C++ & C have footguns everywhere that generate billions of dollars of security industry work.

What would you call the footguns of lisp & golang?


Right now, Denzel's points are spot on. I would add Go the compiler itself is something of a footgun. It routinely compiles things it shouldn't. Go vet is the bandaid on this.

Common Lisp's biggest footgun is probably its lack of opinion about anything. It's truly multiparadigm and probably not in a good way. Alan Kay: "Lisp is not a language, it's a building material." A real world example: I had beers with the SIFT guys (a local AI consultancy here in Minneapolis) a couple of years ago. They explained to me that when they have a problem, they go and type text in an editor that explains the problem formally. Then they type words in that problem's "language" that allows them to express a solution. Then they go an implement that language they just came up with using Common Lisp.

Clojure on the other hand is a Lisp with an opinion: parallelism can be done in the same idiom as non-concurrent logic if you use immutable constructs. So the language will push you in this direction. It turns out writing code without branches, variables, objects and yes even state can be done 99% of the time this way. And yes it is simpler in the truest sense of the word.


Off the top of my head, for Go, (1) the semantics around nil and closed channels resulting in deadlocks, and (2) failing to recover from an unexpected panic. I think error handling and documentation of the errors returned by methods could be improved in Go. I dislike having to comb through the Go standard library source to see what types of errors a method returns.

I can't speak for lisp because I don't have enough experience with it.


Go also has the exact same NPE footgun Java has, IIRC.


Fair enough. I'm reminded of Hickey's talk on the difference between "simple" and "easy". I just did a CLOC on the latest golang github repo: its now at 1,000,000 LOC. The Clojure repo (which is considerably older)? 40,000. Talk to me in a few years when you tire of this.


That's not exactly fair. Go implements a runtime, GC, etc. all of which Clojure inherits from the JVM or the JS implementation.


That's a "trueism". However, the CLOC was on the Go part of the repo. Go's GC isn't implemented in GO.


Isn't the runtime implemented in Go since 1.5? A quick overview of the C content[0] shows that the C source in Go is mostly cgo (either test cases or the runtime integration[0]) and the shootout C sources (for bench tests?).

[0] https://github.com/golang/go/tree/master/src/runtime/cgo


You started learning Go a month ago and, as a total beginner, you've written 1,000 lines of code per day in it? (20000 / (4 weeks * 5 days)). 125 lines per hour! This is a somewhat unbelievable figure.

I'm also skeptical about coming to decisions like this:

> And it's good enough in enough areas for me to set Go as the standard, de facto language at my company, unless there's a damn good reason to use another language.

...based on a single, one-month, one-man project. That's like deciding to only use a hammer to build everything from now on just because the hammer worked so great with a birdhouse.


I work 14 hours, workout for 2 hours, and sleep 6 hours a day, 7 days a week. This is my company and I'm bringing it into existence. Not to mention that LOC count includes testing which I write in standard TDD fashion. Furthermore, a number of prototypes were built in Ruby prior to working with Go. And the design for the core set of services was thoroughly planned and documented.

While I may be a total beginner in Go, I am absolutely not a junior software engineer. Go lends itself to being learned quickly.

No. It's not based on a single, one-month, one-man project. It's based upon a series of pain points I've felt over the years working in a number of different languages, on a number of different projects, with a number of different teams. Go solved enough of them to be worthwhile.

Your simile is flawed. It's like deciding to use one hammer to hammer all nails from now on until it breaks. I'm optimizing for aggregate throughput of a mid-sized team working on the problems we have over the course of 3 years. Go fits the bill for all the reasons I discussed above.

If you want to offer up a more detailed point-of-view, please do. That'd be much more appreciated than such a vapid, dismissive piece of criticism.


I've found that there are only two reasons I like Go: 1) It's concurrency model was a revelation compared to anything I did in the C world. 2) It's opinionated.

Everything else is a bonus. In Go, there's only one way to format your program correctly, there's only one way to document your program correctly, there's only one recommended way to serialize to JSON, it only has one (IMO really awesome package management system) etc. It's remarkable how little thought I now need to put into those things.


You owe it to yourself to at least try a modern, strongly typed functional language (Ocaml, Haskell, F# or Scala). If you go far enough that you understand the advantages of such languages, and still decide they're not for you, then fair enough. But there's a whole world out there and it's incredibly frustrating to see people talk as though Go is the best language when they don't seem to even know what the possibilities are.


I loved working with F# for windows development but it wasn't a viable option for most of my projects.

Ocaml is also a great language but it had problems with poor standard libraries and concurrency support.

I consider Haskell a beautiful language, but the most challenging for me to reason about and understand.

Scala has always seemed like an abomination to me, but I haven't used it enough.

I now use Go for most of my professional projects. It is just simpler than the languages you listed.

The learning curve is smaller, and, I am typically working on projects with other programmers, most who have never used a functional language and many are openly hostile to that paradigm.

Also, most of the projects I work on don't need the more power features of functional languages.

If the project's complexity is high, for instance, a derivative pricing library, or if I am working with a small team with previous experience with functional languages, or doing a personal project, then I would consider using a different tool.


Every programming language starts out like that though.

It's just that over time needs/requirements change and then suddenly you have another package manager, another JSON parsing library etc.


As a counter argument I would point to C, C++ and Lua as examples of languages that try to keep a basic generic standard library and let everything else fall out in third party libraries. I consider Go more in line with the C family then with the scripting languages like Python etc, With a slightly bigger stdlib.


I disagree strongly. You should keep into account that Go is a very young language, and hasn't had to deal with historical baggage. It's still the hot new thing, and that just won't last.

1) Concurrency model : there are plenty of C++ frameworks doing various concurrency models. As long as you do what Go does, and only use that framework, no linking to other stuff, it works beautifully. Already, the standard library is making allowances for other synchronization primitives (look at the sync package)

Of course, in C++, you'll be sorely tempted to link in code with other concurrency primitives. Especially when it comes to having 2 kinds of event loops, you really shouldn't do that. It doesn't help that a lot of large companies felt the need for 2-3 proprietary C++ event loop systems. Fortunately, most libraries aren't parallelized, so it doesn't really matter what event loop system you run them under.

Also, from working with other concurrency models ... I miss a lot of stuff. Futures ... oh my God, does Go need that (why doesn't "go func()" return a future ? AARGH). Limiting resources for specific functions like java's executor framework ... when you need it, there's no going without it.'

Also, select is a horrible way to implement protocols (rules-based communication between either different parts of the same process or simply other parts of a distributed system). Implementing a protocol as a set of methods that can be called on a given object, with or without remote object references works much better. Incidentally, this is what all Go protocol libraries do (google protobuf, cap'n proto, flatbuffers, ...). I understand why they choose this approach over the "native" Go approach : select isn't useful for anything but the most basic of protocols. If you have more than 2-3 message types ... things start to get completely out of hand with select.

2) only one way to format your program correctly ?

I think you'll find that gofmt in fact leaves many questions unanswered. Line break or no ? Up to you. Where ? Up to you. Full syntax for struct initializers or short syntax ? Up to you, gofmt won't change it ...

What I will credit gofmt with is popularizing the concept, and getting code formatters much more opinionated in other languages, and that is a very good thing. But LLVM's C++ formatter and autopep8 are superior in functionality to gofmt for their respective languages.

3) I have seen at least 5 different JSON serialization libraries in Go. There's 3 broad approaches.

You can either use reflection on structs and then use that to decode JSON. Trouble is, this is slow ... very very slow. It's as slow as the next option, but doing things this way does mostly ensure correctness (doesn't deal with various kinds of ddos though). There's just "decoding" JSON into map[string]interface{}, which is about as fast but it can deal with fields unknown at compile time, which can be important if you're making something that, say, just checks one aspect of requests (e.g. security, or load balancing). The huge disadvantage is casting interface{} to what you need every time. And finally there's precompiling a given JSON format into a Go library, which is a LOT faster than either of the previous approaches (but also can't deal with unknown fields).

4) Package management ... lacks versioning. There's no guarantee your code will compile 6 months later if you use Go's own package management. In fact, given just how in flux everything still is, it's pretty much guaranteed to not even compile.

Also, a number of Go advantages are only advantages because Go is only beginning to exit it's honeymoon phase. Go libraries, whether builtin or on github, are "v1" designs : they're consistent, they don't have historical crap in them, they don't have 20 unforeseen but necessary usecases crammed in in extremely uncomfortable ways. Not yet, that is : the honeymoon is ending. Bad design decisions do bite larger programs on occasion (such as Go's logging not using an interface, which can bite you, leaving you with little choice but to reimplement logging for your project/company). This means that while things are pretty good now when it comes to the standard library and most github libraries, they're getting worse fast.

Go libraries are written by enthousiasts mostly at the moment. So while ignoring errors occurs regularly in those libraries, it's not yet like C, where every single error is ignored in the vast majority of libraries.


> such as Go's logging not using an interface, which can bite you, leaving you with little choice but to reimplement logging for your project/company

Ah yes, another pet peeve. The Go standard library sometimes uses package functions, sometimes a struct with functions and then -- sometimes, not always -- has an interface that goes with that struct.

Whenever you have a library with the interface, you can fake it out for testing. When it hands back a physical struct, you wind up wrapping it.

Any time you wander into a repo drop from a company, you'll find standard lib wrappers, all written from scratch, because of this.

Golang interfaces are a strong feature of the language and they make it possible to practice more isolated unit testing. The inconsistency across the standard lib is very frustrating. Frankly I'd rather be stuck with no interfaces than the mishmash I faced.

I wanted to like Go. I really, really did.


But LLVM's C++ formatter and autopep8 are superior in functionality to gofmt for their respective languages.

I was under the impression that some of the syntax for C++ and / or C is a bit ambiguous, which makes it difficult to parse correctly, and to make source code transformations that will absolutely, positively not change the semantics of the code.

At any rate, getting more or less the entire community onboard with using one particular code formatter is the really hard part. I'd say it is impossible to do with existing languages. I don't see how the majority of in-production C++ code, or Python code will ever be formatted in one fixed style.

So even if superior formatters exist in those languages, it doesn't matter, because the majority of code I will encounter out in the wild (open source or not) will not be formatted in with a single convention.

If there is one lasting legacy from the creation of golang, it is hope that for all new languages, their communities will adopt a single code formatting convention. I hope that everyone, whether or not they like golang itself, sees how important that is moving forward.


>I was under the impression that some of the syntax for C++ and / or C is a bit ambiguous, which makes it difficult to parse correctly, and to make source code transformations that will absolutely, positively not change the semantics of the code.

That's for naive parsing of C++, like some do for syntax highlighting etc -- not for LLVM's C++ formatter, which uses the AST of the fully blown C++ compiler et al.

If that parser didn't get everything right, then LLVM's C++ output will also be borked, not just the formatter.


I'd contend that abstraction is perfectly fine, so long as it's an abstraction that people reading the code are likely to be familiar with. In Haskell, I'd prefer someone just use a monoid when appropriate, rather than reinventing the wheel. That said, you might want to avoid some of those Haskell niceties when writing Scala (as my co-workers keep reminding me) because those abstractions are less familiar in the Scala community.

As always, the key is to tailor your message to your audience. That's just as true when writing code and choosing your abstractions.


>For someone new to the codebase who knows nothing of the giant abstraction, which do you think is easier to grasp: 10 lines of code that only make sense if you know the abstraction, or 20 or 100 lines of code that can be understood on their own?

The "only make sense if you know the abstraction" part seems to have it backwards. The point of the abstraction is to _not_ have to know what's being abstracted away. The problem is that with Java (which Go was being compared to here), abstractions often times aren't exactly abstracting away anything. "Abstraction" has basically come to mean "use objects because they have getters and setters!" and other things like that, which give very little benefit in general. Nine times out of ten, they do make the code more complex, because their abstractions come either at too low a level (abstract away simple functions but require the code on the outside to implement everything and reinvent the wheel) or too high a level (abstract away all operations so that no creativity is possible with this). So as a solution to the "abstraction" problem, Go does it right, but something like Rust (which doesn't try (too hard) to be like C, Java, etc.) can do an even better job at this by entirely changing the meaning of "abstraction".


"In real-world code, you don’t actually end up casting interface{} to something else that often"

I have to disagree here, and I've had trouble finding any serious argument that this is an OK thing to have to do. When we accept the benefit of not allowing users to create generic abstractions, we also have to recognize the very real cost of not allowing users to create generic abstractions.

Software/technology trends go in cycles. Several years from now, a generation of Golang programmers will be preaching the benefits of DRY, having learned from the painful mistakes of maintaining the large and complex Golang codebases yet to come without the benefit of creating appropriate abstractions, just as Golang was partially an answer to the painful mistakes of maintaining large and complex dynamic codebases. And so the pendulum will swing again.


    Whenever I work in Rust, I find myself having a good time mucking around 
    with the abstractions, but not really getting anything done toward the 
    problem I’m trying to solve.
I hear this a lot.

There's a lot of good stuff in rust, but people seem to get massively caught up in making the borrow checker happy and lose sight of the actual task.

Maybe the idea of a drop in GC type for rust (ie. https://github.com/Manishearth/rust-gc/) has some merit after all...


The original post was talking about abstractions. The borrow check is not an abstraction.


Ok, sure:

    Making the borrow checker happy with their abstractions...
You've seen iton #rust too, Im sure. People trying to make a genric reusable thread safe Foo and getting stuck with the borrow checker rather than making a specifc solution to a problem that uses unsafe or whatever and actually solves the problem.

I know Ive seen it several times.

Safe good code is better, but it's also a distraction in many problem domains.


A safe GC would be immutable by default, so you end up with a lot of Cells (or worse, RefCells) to emulate a mutable GC like JS or Go.


Pretty easy to do `type MyGc<T> = Gc<RefCell<T>>`.

Even better, put trait objects everywhere. Now we're Java. Go sit in a corner and think about what you've done :P

When designing rust-gc with mystor at one point I joked about writing a syntax extension that converts everything in a crate to a GCd, RefCelld, trait object, so that Java programmers have no problem converting.


I just want to add my 2 cents.

Abstraction is fine, and useful, however every abstraction comes with a cost. Even if you can't see or quantify the cost that does not mean it isn't there. It only means you have not done the work to fully understand it. It is your job as a rockstar dev to ensure the benefits of using abstraction outweigh the costs.

And that brings us to Go. Go is a perfectly capable language. Like most it's good at some things and not so good at others. If you approach it expecting Haskell, you will fail. If you approach expecting Go, you will have a much better experience (assuming you are working on a project that is a good fit, once again, your responsibility).

I won't tell you to use or not use Go or Haskell. They are both awesome, and they both utterly suck. But you know what? That's life. That's how it is always going to be.

Pick your poison.


The timing is funny, I just started playing with Go a few days ago (the 1.5 release as well as the impressive amount of software that's actually been shipped with Go convinced me to give it a go - pun intended, somewhat).

I must say, its a great little language. Love the tooling, the dependency management, it was even incredibly simple to set up the dev environment (basically just DLed the binaries, added to my path, installed the Go-plus package for Atom, and everything just worked).

It is a little shocking to work with a relatively bare-bones language since I work mostly with R (which has a package for everything it seems), but its been a lot of fun. I'll probably do something bigger with it in the next little while.


And fortunately , there are plenty of alternative languages (D, Rust, Nim, Crystal,...) , so let's promote them instead because any criticism Go will fall into deaf ears anyway.


Yeah,especially Crystal is gaining traction and lots of good commits are coming in.


Is Crystal's whole point to be a staticly typed Ruby, or is there more to it?

(I guess I'd just like some of the things we've learned from functional type systems, such as pattern matching, which I don't see up front.)


Well that's the starting point. Currently more ideas are forming up and being baked into the language.

For example Crystal is removing explicit Threads and switching to Channels as the main concurrency model.


> In many cases, that up-front cost is paid back many fold later, when other programmers (or you yourself, after you’ve forgotten the details of the code) come and try to read and work with the code. Go optimizes more for understanding the code later than for building big things quickly.

I believe this sounds attractive in part because programmers are so excited by the idea of solving problems, and it's true that a specialized implementation can always beat a general one. But it does not sound particularly sustainable.

A language that doesn't provide adequate abstractions often results in requiring its users to develop those missing abstractions over and over again. With the requisite possibility of bugs or implementation naivety. As both a Rust and Go user, I do find the talk of generics a little over played. Go has great facilities for code reuse that often mean you won't miss generics as much as you might think. But I also see Go projects growing in size quicker than I can appreciate. The increased volume of code, each piece being specialized for its particular purpose - harder to fit in my head at one time. The cost of changing the code becomes more severe, each instance of an abstraction in the code being different enough to warrant specialized attention, rather than fixing code in a single place.

In every organization I've worked at, as the size of a code base grows, so does the cost of change. A language that doesn't scale well is only going to exacerbate that problem. Perhaps Go seems great now because we've yet to really see any projects written in it at this scale.


There'a real problem with abstraction. Look at any Java code. If you haven't seen it before you are going to deperately browse around the code to find out a single line that actually does something. But all you see is scaffolding. When you finally find a line that does actual work, it's not clear how it connect to other such lines. There's another such line 1000 lines below it but all the abstraction makes it almost impossible to find out how are the two executed. Which one goes first? Is one of them optional? Et c.

In general, abstractions are used to capture programmer's understanding of the structure of the program (it's more or less like boxes and arrow sketches in your notebook). But then, programmer's understanding of the structure of the program is often inadequate and lacking. As your understanding of the problem improves, so does your abstraction. But by then, it's hard-coded and cannot be easily changed. Many times I've seen how new functionality cannot be implemented because it doesn't fit the abstraction and re-writing the abstraction would mean re-writing the whole project.


> But all you see is scaffolding. When you finally find a line that does actual work, it's not clear how it connect to other such lines. There's another such line 1000 lines below it but all the abstraction makes it almost impossible to find out how are the two executed.

Wow, but as you say, that's "scaffolding" (or "boilerplate"), not "abstraction".


As I understand it, the scaffolding in Java exists to express astraction, no?


From what I've seen most of the times people complain about Go it is because they think they're going to use whatever feature they claim is lacking when in actuality they rarely use it in practice.


> when in actuality they rarely use it in practice.

Because they can't at first place since it is lacking. If it wasn't lacking they would design their code around this "whatever feature" they feel is needed.


I like rarely used features. A language having extra features doesn't make it worse. I've used maybe 5% of the python standard library, but that fact doesn't make me feel like Python is worse.


There's a big difference between "features" and "libraries".


Care to explain? As an example of a rarely used feature I'm glad to have: Common Lisp macros.


The article spends a lot of time talking about how "Complicated" abstractions are, and about how abstractions come with a complexity cost. IMHO, the opposite is true.

> Whenever I work in Rust, I find myself having a good time mucking around with the abstractions.

Good abstractions, the kind you use in Haskell and (presumably) Rust are simple, non-leaky, and exist to enable simple, correct code.

It's true that abstractions, even good abstractions, take time to learn. However, once you've digested them, you'll see them everywhere, and you can continue to use them for the rest of your life. Functors, for example, are a foundational abstraction. They are extremely simple, extremely powerful, and will relevant forever.


It's been interesting to watch similar arguments being made of Golang, over and over. From a casual user's perspective, it seems to optimize for (what is to me) the wrong thing, which is readability over maintainability. Without the ability to create abstractions we run the risk of having to re-learn the lessons of the past, about DRY and SRP and all that jazz. While more language features give users more rope to hang themselves with, less language features constrict the ability of larger projects to manage complexity, such as is the case with dynamic languages. For Go, I sense that this will become more obvious over time.

TLDR; Readable code is nice, but maintainable code is better.


great ending!

"Typing is not what makes programming difficult."


Nim (http://nim-lang.org) is far superior to Go and Rust in many ways.


Nim is interesting but isn't remotely like Rust. It doesn't have memory safety as a core goal. And there's no strong sense of design and elegance. It very much comes off as "here's a bunch of ideas thrown together with the restriction that they all must somehow compile to C".

I'm not saying it's bad or its users are wrong, but the mindset is utterly different.

Rust and Go don't belong in any comparsion really. The only reason they are compared is because Google co-opted the term "systems" to mean something different than everyone else. Go fits in much more compared to managed languages, like Java or OCaml or something. (Just because Go statically links its runtime into output binaries doesn't change things. You can do this with C# via Mono's AOT features, for instance.)


> And there's no strong sense of design and elegance. It very much comes off as "here's a bunch of ideas thrown together with the restriction that they all must somehow compile to C".

But C is Turing-complete, so there is no restriction here. It's also not a "bunch of ideas thrown together" anymore than Haskell or Scala or Rust is. Nim focuses on 3 things:

* Metaprogramming via a hygienic AST-based macro system.

* An effect system to make the type system simpler to use. (The compiler infers an awful lot of useful information for you, the docgen shows the results for everybody to look at.)

* GC'ed thread local heaps where each GC can comply with soft-realtime requirements. Hard realtime is being worked on.

Any modern statically typed language also needs to have generics, closures, inheritance, exceptions or workarounds of the same complexity, so yeah Nim also has these making Nim instantly a big language. That doesn't mean it's a "bunch of ideas thrown together".

> It doesn't have memory safety as a core goal.

It does and it is reasonably safe (yeah yeah yeah we don't check for 'nil' properly yet, give me a break) with quite some improvements in the pipeline.


My apologies then. I didn't mean to come off rude; elegance is subjective I suppose.

I was under the impression that the only safe pointers (references) must be GC'd. So if you want/must to avoid that (say for perf), you're stuck using regular unsafe pointers. Is that inaccurate?

The manual even says that just calling printf is actually unsafe as the cstring could be GC'd (but it probably won't). Maybe Nim should have an unsafe directive to make sure any unsafe user code is clearly marked?

I also coulda sworn there was some part of the manual or site that was discussing a feature with a limitation due to the difficulty of expressing it in C. Maybe I imagined it. Sorry.


> The manual even says that just calling printf is actually unsafe as the cstring could be GC'd (but it probably won't).

The manual tries very hard to mention corner cases since it's evolving into a proper spec. Calling printf is safe, it's C functions that take ownership of the char* pointer that are inherently unsafe.

> Maybe Nim should have an unsafe directive to make sure any unsafe user code is clearly marked?

That's a common misunderstanding, Nim doesn't need this. Instead of an ``unsafe`` keyword, Nim has ``addr`` and ``cast`` as keywords, these are the unsafe building blocks of the language. There are other corner cases that introduce unsafety, but they are all known and can be solved in time.

> I also coulda sworn there was some part of the manual or site that was discussing a feature with a limitation due to the difficulty of expressing it in C.

Well these things certainly are everywhere, but the issue here is not the difficulty of expressing it in C, but a desire to have very good C interop. Rust actually has all the same design constraints even though Rust builds on top of LLVM because the constraints come from the "systems programming" problem domain: Control over memory layouts, the stressed difference between heap and stack, etc. LLVM's IR is very close to C, so what is difficult to map to C is difficult to map to LLVM too.


> It doesn't have memory safety as a core goal.

There is always a trade off in memory safety vs. performance.

> there's no strong sense of design and elegance.

I definitely see more elegance in Nims syntax as compared to Rust or even Go. But maybe this is because in real life python is by far the most productive language for me and Nim represents something like sane Cython alternative.


> There is always a trade off in memory safety vs. performance.

Well... no. The central premise Rust is to not make that specific trade off. Rust gets memory safety without a hit in performance. (It does of course make a trade off: memory safety comes at the cost of more compile time checks and a more sophisticated type system.)


>Rust gets memory safety without a hit in performance

Interesting. How does rust statically verify that array accesses are in bounds?


Rust defaults to run-time bounds-checked array accesses, but iterators mean that the bounds only need to be checked once.


>iterators mean that the bounds only need to be checked once.

How is that possible with random access or mutable containers?


A Rust object can be owned (T), mutably referenced (&mut T) or immutably referenced (&T). To a given owned object you can create either a single mutable reference or any number of immutable references (concurrently, sequentially is fine).

When you create an iterator from a container, the iterator contains an immutable reference to the container. As long as the iterator is alive, it's not possible to take a mutable reference to the container, and thus not possible to modify it. Because a reference can't outlive its source, this also ensures the iterator can't outlive the collection.


Array accesses are checked at runtime.


> The only reason they are compared is because Google co-opted the term "systems" to mean something different than everyone else.

Really? You know, for a fact, that that is the only reason the two languages are compared? There couldn't be any other possible reason?

How many years has it been since the official literature made such an egregious and offensive mistake as to misuse the term "systems"? The only reason it's even an issue is because people insist on bringing it up as the root cause for [insert social phenomenon here]. Because clearly, anyone who compares these languages must be so enamored by the original description of Go as a "systems" language that there couldn't possibly be any other explanation. (But maybe this fits in just right with your view of the Go community as "anti-intellectual.")

Maybe people compare the languages because many people find them comparable. There's no mystery there.


There's no mystery there.

Contrary to the previous poster, I suspect people simply compare the languages because they appeared around the same time and are both backed by big players.

There's nothing really comparable between them (although the that's just my opinion) and I'm baffled as to why they are co pared so often.


Any two languages can be compared. The notion that the act of comparing two languages can be traced to one particular root cause (such as the use of the word "systems" N years ago) is ludicrous to me. What matters is the analysis that is involved in that comparison.

For example, I'd find the answer to this question very interesting: "Compare and contrast concurrency in Rust and Go." I contend that the answer to this question is epic. It will take you back through the history of PL and lots of really interesting design decisions. At the forefront: should concurrency be a first class part of the language? Or should it be part of a library?

And that's just one interesting question. There are plenty more.


Right from the article:

> When I initially wrote down my reactions to Go, I didn’t know about another new systems language: Rust.

But maybe I'm misreading (honestly).

Still they end up getting compared an awfully lot, more than they're compared to closer languages. At any rate, yeah, I find it very weird that the languages are compared. There seems to be very little overlap in when you'd want to use them.

If you aren't bound by C-linkage or zero-runtime/overhead requirements, then there are higher level, more flexible languages to use than Rust. If you do have those requirements, Go simply isn't an option. (This is a bit simplistic but is generally correct.)

Edit: I see you're taking an overly literal interpretation of "only". You're right. Anyone can compare anything for any reason. I think the intent of my comment is clear enough.


Your reasoning is not just a bit simplistic, it's overly reductive. Just because I'm not bound by C-linkage or zero-runtime/overheard requirements doesn't mean Rust isn't a good choice. Similarly, even if I do need to link to a C library, that doesn't mean Go is a bad choice.

> There seems to be very little overlap in when you'd want to use them.

If all you're going to do is consider each respective language's wheelhouses, then I might agree that there is little overlap (at least, currently). But in my experience, there are often plenty of other reasons in play that influence language choice. I once chose a language (not Rust/Go) purely because of how popular/accessible it was. There was no technical merit there, but because of that choice, the project has gotten very popular and used by a lot of people.

At work, a few months ago, we had an internal debate over whether to use Rust or Go for a new project. There's plenty of overlap. Not every problem has a workable solution governed by uncompromisable requirements that uniquely identify a language, so one invariably winds up adopting other criteria for choosing a language. These criteria may not be uniquely technical; they may be social or even emotional.

I contend that nothing I've said here is controversial. My goal is to encourage others to broaden their horizons; we don't need to put our blinders on and snub our noses at people that do things differently. (Which is absolutely how I perceived your comments in this thread.)


While I don't mean to snub anyone, it's sorta like C++ - most would not use it if there was a better option (C compat, little runtime/overhead).

Go cannot (AFAIK) be used when you need C compat (i.e., I can't write a C-compatible plugin in it, whereas with Rust I can).

I'd contend that "GC or not GC" is _probably_ a rather large requirement. Rust is nice enough that some may use it when they don't really need it.

But fair enough, if my comment came off snubbish, my fault.


I like Go, I've coded a few personal projects in it in the past, and generally like to keep up with the Go scene when I can.

However, I can't help but feel that if people want it to replace Java, then they need to make a robust IDE like IntelliJ or Eclipse.

I'm a Vim user at heart, but when coding in Java I find IntelliJ + IdeaVim plugin to be very productive. It goes far beyond just the classic auto-complete too, IntelliJ does a lot of neat stuff like suggesting updates to the code, or creating fields/methods and refactoring just as a simple key combination.


If Go has any legs, I'm sure that JetBrains will get around to building a version of IntelliJ for it. They've even got empty space for it in the IDE section of the products menu on their site :-)


For the meantime, there is a very nice Go plugin for IntelliJ products:

https://github.com/go-lang-plugin-org/go-lang-idea-plugin


I think if/when Go adds some form of generics (beyond requiring code generation with comments...), it will truly replace Java.




Consider applying for YC's Summer 2025 batch! Applications are open till May 13

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: