Changes I would make to Go
sitr.us
sitr.us
Then it seems that Go isn't the language for you. With that Go nor any language, unless intended, is suppose to be a one-all solution for every problem. We can accomplish a majority, if not all, problems in a language, but this shouldn't assumed that a language is intended to do so.
ys := []byte{}
for _, x in xs {
ys = append(ys, f(x))
}
over something like this code, which is more obvious, less error-prone, and, though it relies on an additional concept, it's an extremely broadly-known concept: ys := collections.map(xs, f)
I don't think a proper collections library would require very invasive runtime changes -- though, once you start down this path, you and up at "but what if `f` returns `(myType, error)`, and then you're wishing for Option/Maybe types (which is another feature that I believe Go should have implemented, but is more obviously outside of the apparent mandate).EDIT: It would make static analysis significantly more complex too.
If they build a collections API like this now, without generics, and they then add generic types later, they will be in the same position Java found itself in - lots of painful API migrations.
While the generics debate is legendary in go, it does still seem very much like the intention is to eventually include it. Given that, I think it makes perfect sense to wait to introduce more special cases (like `append`).
In the meantime:
func Map(in []interface{}, f func(interface{}) interface{}) []interface{} {
out := make([]interface{}, len(in))
for i, v := range in {
out[i] = f(v)
}
return out
}
:)Generics do make a lot of problems easy, but I don't think they inherently make problem solving simple. I absolutely miss them in Go - but I also remember how confused my high school programming teacher and I both were about them.
So far, Go seems to err on the side of Simple, if a choice has to be made. And, so far, the proposals for generics in Go seem to force that decision. I'm glad to see how seriously the Go authors take weighing the design and decision to include them.
I would strongly disagree with the definition of "simple" that you seem to be using. I don't think it's important for a language designed for software professionals to have only features that are easy for high schoolers to understand. It's far more important that experienced developers be able to write a common idiom like map with as little cognitive overhead as possible, that's the meaning of simple that I find valuable in this context.
The fact that append exists and the language would be extremely convoluted and inefficient to use is a testimony to how useful generic programming is.
Yes, when this topic comes up I think it helps to remember that the Go devs are in no way opposed to generics. They have no philosophical "thing" against it, and it's not about "our ideology and where we want to go with Go".
https://golang.org/doc/faq#generics
It's more like... It's complicating the type system and a hard problem to solve. If they do it, they want to do it right, because a main goal is to not get complicated.
There are plenty of programmers for whom this is cryptic, and they prefer an old-school "simple and straighforward" for loop (typical of imperative programming).
What makes map more cryptic than literally any of the abstractions already in that example? Make is abstracting away GC-based memory allocation, indexing is abstracting away pointer arithmetic, and so on. Why are those abstractions fine, but "apply this function to everything in a collection" is somehow impenetrable voodoo?
"Simple and straightforward" often just boils down to "reams and reams of boilerplate code", where catching bugs is hard because it's impossible to notice a slight errors in one out of a hundred reimplementations of the same function.
I've never really done functional programming, and map was cryptic the first time I saw it, but it's literally a concept that can be understood by any competent programmer in under 30 seconds - apply a function to every element of a collection and return a collection containing the results.
I don't have any sympathy for programmers who find simple abstractions like this cryptic but then don't try to understand them.
It would only make sense to make that point if they were intentionally left out of Go because they were regarded as having no significant value or even negative value. And that, I think, is the widespread perception of why they aren't supported in Go.
We can accomplish a majority, if not all, problems in a language, but this shouldn't assumed that a language is intended to do so.
The programming methods described in the article can be applied to almost any problem that Go is a good fit for. If that's in question then I think it proves my original point — this argument is not about arguing the tradeoffs; it's about whether anything of value was left out.
* Pattern matching is intuitive and can be extremely powerful. * It's pretty clear that Null should not exist anymore. * Multiple return values, but no tuples?!
The go designers seem to not have a problem with special cases so why didn't they include special cases for an Option<T> and Result<T,E>, two things that, with pattern matching, would fit right into Go's explicit error checking philosophy.
The current position is that the core team will only include generics if they can find a design that satisfies them. Which has sounded a lot like "no, never" but Russ Cox has said that he wants to look into implementing generics again this year.
I learned from Rust that I dislike curly braces. The language itself seems great conceptually, but I prefer the appearance of languages like Python that drop curly braces. If Rust was developed without curly braces I'd probably be [more] obsessed with it.
Strangely enough, parentheses and square brackets are also appealing. I also dislike writing code where whitespace is meaningful, even though I prefer to read it.
It's just the appearance of curly braces that bothers me.
That sounds easily fixable with a different font. Or make your own. :)
I think one tangibly good thing about Go though is how readable it is and how easy it is to grok, even with all the error handling boilerplate. I'd argue that Rust is also very readable though, and if the features of the language appeal to you I'd suggest giving it some time to get over the visual hurdle.
From a personal standpoint having spent years working in both languages, it's rare I'll choose Go over Rust nowadays purely for practical reasons. I'm roughly equally productive in both, but the end result tends to be much more performant, maintainable and robust on the Rust side.
But where I thought he brought up really good points are
1. nil. He's right compared to more recent languages, nil is awkward. I've been bit multiple times by the 'nil checks sometimes fail.' concept. Option/Sum types are so much cleaner, and having non-nullable types make a lot of things easier to reason about.
2. error checking. My main gripe is when you are writing a process that involves calling multiple functions that could return errors. You then need more boiler plate in order to stay sane and DRY - and I've admittedly bitten myself with the 'err' shadowing that happens sometimes.
I don't think a Maybe type would have made Go more complex, and I think it would have fit in nicely with the 'Zero Value' concept.
If the language designers thought an Option/Result/Try was valuable they could add it. Just like they could add Futures/Promises and concurrent safe maps if they wanted to.
This is indeed a problem. It's the same problem with people writing a catch-all exception in Java, then forgetting to write the correct handler later. Considering the Go team's priorities, it's one they should fix!
val, err := foo()
if err != nil
return err
vee, err := bar()
if err != nil
return err
Would become: var err error
whenever err != nil
return err
val, err := foo()
vee, err := bar()
Static analysis would insert the appropriate check any time err is assigned in the function. Aliasing might be a gotcha, but I'd bet that is a rare case and static escape analysis could maybe catch it and either error, or just insert extra checks.What I want? I want to
a) have an option where an unchecked return error causes the program to log and exit.
error SomeFn(int blah, int haw)
{
if(blah < 0 || haw < 0)
error(1);
return(bah*haw);
}
// program will die here
SomeFn(-20, 23);
// program will not die here
if(SomeFn(-20, 23) != 0)
{
// something
}
b) be able to pass a label to a function to handle errors.Example
int SomeFn(int blah, int haw, error oops)
{
if(blah < 0 || haw < 0)
oops(1);
return(bah*haw);
}
int res = SomeFn(-20, 23, crap);
printf("res =%i\n", res);
// SomeFn returns here
crap:
print("parameters can't be negative");If there is a way to incorporate the best features of Haskell and Rust while maintaining Go's current simplicity and ease of use, then it should be incorporated. To that end, the Go core developers have expressed an intent to implement Generics, however they want to do so in a way that does not (significantly) negatively effect the language's simplicity or compile times.
Is that documented somewhere?
It's something I hear often, though. That Go is intended to be a "modern" language, with the definition of "modern" being that the opportunity that a blank slate provides allows for the application of the best features and learnings of the languages of the past.
Application of the best features and learnings to a particular problem domain. The (intended) domain is Google-scale code bases that live for decades. The ideas that are important there may not be applicable anywhere else. If they are, it's more by happenstance than design.
I am in two minds; after all, C++ existed for a long time without a workable implementation of templates, and that hasn't stopped the STL from doing quite well.
On the other hand, there were a number of fairly successful generics implementations already done when Go was created, and some of the core developers for Go seem to really have only lukewarm support for the idea (unlike C++, where Stroustrup seemed to support generics enthusiastically well before they appeared).
It does seem to be a very weird thing to bolt onto an already mature language. Some of the less felicitous bits of the C++ standard library seem like they would have been done very differently if templates had worked from Day 1 in the language.
I'm not an expert in generics by any stretch of the imagination; I timidly implement the occasional template function, happily use STL/BGL where I can, and am amazed at the sort of data structures this lets me get away with.
I do find myself puzzled that apparently no generics system in common use was deemed 'good enough' by the Go team to just clag before an entire language and its aforementioned 'rafts of code' sprang up around the absence of such features... there may be good reason.
But a language that doesn't let me do what I happily do in C++, with type safety, using language features that are over a decade old, is a total non-starter for me. The language may be all the simpler for this, but the code I would have to write would bear the cost of this instead. It's not a win for me, and vague threats that I'll get a vomitous compiler message when I bugger up "make_pair" and hints that the "princess is in another castle" aren't going to browbeat me into accepting a throwback of a language. My guess is that g++'s template error messages are going to be acceptable long before I see generics in Go, if I ever do.
The Go team wants generics. They acknowledge that generics would be a benefit to the language, and they have acknowledged that generics are by far the most-requested language feature from full-time Go developers. The only thing stopping them from implementing Generics is the inherent (purely technical) difficulty in providing it in a correct, performant, and type-safe manner.
The reason the Go team cannot look to Java, or Rust, or any other languages which provide generics and say "we will implement it exactly as they have" is because Go is as different from Java and Rust as Java is from Rust. Generics had to be individually implemented in each of those languages, and it was a fairly massive undertaking to do so, and there are a lot of folks that say that in Java's case in particular it was implemented poorly but now that it's implemented they're stuck with it.
Like yourself I'm not an expert on generics, either, but everything I've read in regards to generics in Go has lead me to the above conclusion. I trust the Go core developers to implement generics when they have a suitable plan, and I will gladly use them when they are made available, but in the meantime I intend to enjoy all of the great features of the Go language which are available today.
> The only thing stopping them from implementing Generics is the inherent (purely technical) difficulty in providing it in a correct, performant, and type-safe manner.
I initially accepted this, and found the "we don't want to build the wrong thing" explanation reasonable. Some years down the road however that's looking a bit threadbare. I've also read enough conversation threads that it's become very clear a couple of the key decision makers are simply resistant to the idea of consulting with the PLT community for a good design.
I'm happy to wait to Go 2.0 for generics, however, if they yet again recycle this "gosh, it's hard, we just don't know how" I will not be satisfied. There are people walking this planet who know how: ask them.
Doesn't seem so:
https://research.swtch.com/go2017#generics
>That is, is there too much Go code out there written without generics to ever incorporate generics after-the-fact?
You don't HAVE to rewrite all existing code to using generics, although I'm sure it will be tempting :)
All in all, my impression of Go is that it's a language designed by the same people who wrote the Google C++ coding guidelines (https://www.linkedin.com/pulse/20140503193653-3046051-why-go...).
>every language is equivalently powerful
>therefore, we should neither look to one language to improve another
We seem to have made a mistake somewhere.
Let's try again, shall we?
Some languages are trying to serve different niches than other languages. Trying to copy features from one language to another can be useful. But if you're copying from a language that's used for a different niche, you can ruin the destination language's suitability for its own niche.
What's Go's niche? The original intent was that it be useful for multi-million-line code bases that lived for multiple decades. Cost of compile time becomes a very big deal in that environment. (The original motivator was a 45-minute C++ compile. Multiply that times multiple developers, times multiple compiles per day, times 20 years, and it adds up to real money.)
What's Rust's niche? It's lower-level (C++-ish) code with compiler-enforced safety.
What's Haskell's niche? People are probably going to yell at me for this, but I think it's improved ability to reason about the algorithm.
It is unclear to me that an idea that's useful in one of these niches should automatically be assumed to be useful in another. Sure, if you're used to one niche, and you try to cross over to another, you miss things, but that doesn't mean that having it would be a net win in the other niche.
That being said, plenty about Go seems to make it easier to get there: the lack of reasonable tools for abstraction certainly makes copy-paste programming much more likely, along with the ensuing explosion in LOC.
Yes, but you originally asked us to do the opposite: to not modify a language based on the benefits shown in a different language; That an idea useful in one niche should automatically not be assumed to be useful in another.
Which of course is just as baseless as automatically assuming it will be (useful|viable|worth the effort).
Haskell is particularly irratating to see used for that opinion as well, given that is notable for being a research language; a language who at least partially exists to seek out, identify and implement ideas that can be transplanted into other languages, though of course it would naturally take a different shape once integrated.
Not quite. I asked you not to try to turn one language into another (that occupies a different niche). We don't need Go to become Rust, because we already have Rust. If Go becomes a better Go by stealing ideas from Rust, that's good. If Go becomes a worse Go by trying to become more of a Rust, that's a net loss.
Haskell... I admit that I was not thinking of it as a research language, where the whole point is to try out ideas. It's become more than that. It's become a "real" (production) language, not just a research language. But I will grant that it (partially) exists to experiment with ideas and export them.
What it is good for is software that is about moving bytes around from place to place. Network daemons, web servers, cli tools that interact with JSON/Xml in simple ways.
When Bjarne Stroustrup wanted to add OO features to C, he created C++, initially as a translator that output C. If someone wants to add features to Go, they can create a "Go++" to Go translator, with pattern matching, generics, tagged unions. Clearly it's guaranteed to be very popular! /s
Well, if I were the king of the forest, I'd move functional programming from Rust to Go. With Rust, there's a lot of this higher level stuff in a supposably lower level language that would be more at home and useful over in the two letter language.
I feel like you're suggesting that "high level stuff" is bad because it's "high level stuff."
In my opinion, the goal of low level languages is to operate under certain constraints. If you can operate under those constraints and still include "high level stuff," then you should.
I haven't written any of these two languages but I have read Rust doc few times in depth and I constantly miss many of it's features in my daily .net work since then.
Example: -Optional over null -Union types -Trait implementation is independent from source and target models
It didn't. Go offers nothing that a dozen other languages haven't already offered, and fails to include important features that are standard in every other modern language. It is simplistic to a fault.
Dependencies have a cost (even if they are easy to manage). Abstractions have a cost (even if they are easy to write). Consistency is often better than best case performance. Code style doesn't matter. Scriptable tools are invaluable. Make ain't that bad. Enjoying your team is more important than enjoying your language.
All told, I don't think Go will ever be my first choice, everything else being equal, for anything beyond what I like to call "bash++". But it also won't be a reason I don't take a job, or move to a team and if my team wants to use it, I won't object too strongly.
Before picking up Go around the 1.0 release, the last time I spent any significant time with those tools was 10 years prior as an undergrad using Java. Java really made me hate those things. I spent the years in between with languages like Ruby, JavaScript, and Python.
Now I actually prefer using compiled, statically typed languages, Rust being the most recent one for me (and I still dislike Java).
I am not a Java fan either but I felt dynamic languages even worst. Since then I have loved to modern static type systems like in Rust (in theory) and Elm/Haskell.
It got me to reconsider how I view tooling, linters, etc, and my work is better for it.
As far as the language design itself, I do not believe I learned anything new from it.
Go is unusual in the new language space in that in eschewed many of these modern techniques. Swift comes to mind as a comparison, which has many of the features that the author misses.
Personally I like the examples offset by a single other language as it's less context switching.
Go, on the other hand, continually feels like a language that wasn't designed as much as it accumulated. None of the parts seem to work in concert. Nil by itself was a bad idea, but when combined with actually-nil-but-not-really interfaces, it leads to subtly broken code. Tuples feel bolted on as second-class citizens, and tuple-based error patterns combined with sometimes-nonsensical zero values for types makes it uncomfortably easy to accidentally use invalid values when you have a typo in error-handling. Implicitly implementing interfaces makes sense until you type-switch on interfaces at runtime, and then through edits elsewhere one interface becomes a distinct subset of the other interface — depending on how they're ordered, you may never hit the other code.
Some of the decisions made in golang make sense in isolation, but in concert they lead to confusion, subtle bugs, and copy-pasted code everywhere.
What do you find bold about Go's design? (Curious, not looking to start a pointless debate)
It is very strongly focused on one particular approach of doing things. If you happen to like that approach, you'll find Go smooth and natural. If you don't, you'll find Go annoying and pointless.
We're rewarding these posts with attention. I guess on some level, these language wars are gratifying.