Go and Dogma
research.swtch.com
research.swtch.com
All of those things are more valuable the more people that use them, and if you've invested money or time into any of them, you have more of a pay off on your investment if more people also use them. So you might feel strongly incentivized to defend your platform of choice while attacking competing platforms, even if another platform might be marginally better, but not enough to justify the switching costs.
If you're a language developer, you have dual incentives-- one is to get people to use your language, which would seem to encourage you to engage in holy wars, but the other is to be good at developing languages which requires you to think objectively about what you and other language developers are doing, so even if you're publicly defending your choices, you should be open to the idea that another way might be better.
I find the holy wars are usually fueled by "fans" -- people less invested and less knowledgable in the topic. This is true of movies, bands, sports teams, phones, computers and cars.
The fact is if you work on the tech you appreciate that there are tradeoffs and know what your overall objective is (so are less likely to argue about things that don't get you there). Musicians tend to get along -- did the Beatles hate the Stones (OK, the Stones released "Let it Bleed"). I doubt Mark Hamill hates Star Trek or Shatner Star Wars. The catholic pope meets with the grand mufti of Jarusalem. Obama and Bush talked a lot in 2008 and 2009.
It's like the joke about academia being so cutthroat as the stakes are so small. Holy / Flame wars erupt among those on the periphery at best.
There seems, IME, to be a sizable contingent of people who have invested heavily in a particular technology and are threatened, in their own market value, by threats to its distribution, in most technology flamewars. They aren't necessarily less knowledgeable, but frequently prefer depth in a platform to breadth across platforms for developing their own skill set, which creates a strong incentive to promote the platform.
I used to see it as a humorous statement about the disconnected irrelevance of academia.
As a professional academic, I now read it as 'the stakes are so few'. Academic politics are rigidly contained by existing structures and limited funding. There may only be one or two ways to 'win' within the framework, with a limited number of 'winners'. In such an environment, vicious politics may be unsurprising.
The language that I speak defines the limits of my world.
I've always thought it was particularly apt for programming languages.
I personally rather suspect that the weaker forms (that language generally shapes but not fully limits how you view the world) are also equally fallacious, even if language and world-views might be weakly correlative--instead, I see language and world-view as essentially both being strongly derived from that of those who teach you.
For example Mathematics is generally accepted to be incomplete it what it can express (Gödel), and programming languages, while expressive and in theory turing complete, still limit the thoughts you can easily express, which I suspect will guide users to do things a certain way.
Natural language is a bit different because of borrowed words and the flexibility of the humans parsing it.
("diagram chasing" in category theory might be an example of thought being almost eliminated by a notation...)
Something that would only be said by someone who never thought about the time and space complexity of theorem proving algorithms. (Given his areas of contribution, I can't be sure if he ever did or he didn't.)
In other words, not all human cognition is "thought", for Russell. So I don't think he's making any claim that implies total absence of computation with a perfect notation. I'd say he's in the clear on this one.
That's a nice-sounding hypothesis, but it's difficult to falsify. Steven Pinker reviewed some of the scientific literature on that idea in "The Stuff of Thought" and it mostly holds up the null hypothesis.
I believe that hypothesis is true in the trivial sense -- nearly everything you learn affects the way you think -- but I'm not convinced it's true in the more interesting sense.
I really think this is a great post, and emphasizes a strong danger in adopter culture. But I do also think Go, in particular, is rigorously dogmatic in it's tooling. To good effect, in that anyone can come to any project and understand some basics, but the workspace decision is really hard to reconcile with the project-by-project view of the world that most language tooling assumes.
There's not much of that. I think that most people who are put off by that are actually angry that Go didn't affirm their own prejudices.
It has this great automatic interface system! Amazing! Except it also has "primitive" types that can't participate, so many of my functions have to take interface{} instead which is pretty much void*.
It can dispatch to different functions based on the argument types! It will even automatically switch to the non-pointer type if you pass a pointer to a non-pointer function. However, you can't write a pointer and non-pointer version of the same function. Also why are we using pointers to represent reference mutability and optionality these days? Other languages have maybe/option which handles optionality much better.
Speaking of not having maybe/option, every other line of code is "if (err == nil)" and there's no way to abstract this out. Give me `option` dammit. Exception handling exists via panic, but it's a little different than you're used to and the language designers like to pretend it's not exception handling at all.
There are built-in functions that use generics, but I'm not allowed to write one? Hypocrites.
You have to name things starting with a capital letter to make them public. Who came up with this convention? It is not shared by any language ever. It also gets extremely annoying when you want to serialize json, serialize a protocol buffer, or do a database query. Name your struct members in lowercase and those libraries won't be able to see them, but name them in uppercase and the output will be in the wrong case. Instead, you have to "annotate" those struct fields repeatedly for every library you're going to pass them to. You also have to manage "naming convention" systems that translate between the two names, and these conventions are never the same between two libraries.
I wouldn't say Go has structural typing. It has a form of interfaces, which are not the same.
Structural typing is when the data members of an object are expressed in its type. For example, `x : {f : t}` is a statement that the object `x` has a member called `f` of type `t`. That is structural typing.
On the other hand, what Go has is pretty much the definition of interfaces, where you have constraint types that indicate that anything satisfying the constraint has some functions defined on it.
function :: forall a. { f :: String, g :: String | a } -> String
This is a function that takes an extensible record. i.e. 'a' is some type which has fields f of type String and g of type String, at minimum.
Go's structural typing is based on methods instead of fields, but it's structural typing as well.
This in particular is maddening when learning go. I lost hours to this when deserializing JSON.
I remember this biting me when I wanted to write a function that would interpret the value I passed to it, and have a special case for null pointers. I wanted to also be able to pass a non-pointer to this same function name and get the standard case, but the dispatch would not let me do this unless I gave both versions of the function different names.
THIS.
One thing that definitely bothers many people, myself included.
roughly how much Go would you say you've written...?
What point are you making?
If someone writes go for an hour they'll see this pattern.
Error handling in go is more verbose than exception based error handling in some other languages. /shrug
I prefer it, but it's true.
It's obvious the point being made here, regardless of the wrong null/nil thing.
Checking for an error to be nil seems unusual, as you'd continue with your code in that situation.
And the code isn't that long, don't think it really bloats up the code.
The point being made was go has verbose error handling. It does. So does rust; we don't need to dance around it.
Better is a different question, but that wasn't the point the OP was making.
It's the same as not having a ternary operator. Go is no language that allows expressing code in few lines, but by that they keep it simple to read.
Even though I doubt that proper exception handling is shorter than the Go version. Many developers just choose to not do that and handle all Exceptions the same, or using try-catch blocks that stretch over hundreds of lines. It's indeed faster then, but not a good comparison.
This is why I can't take Go apologists seriously when they say the language is just fine without generics. Ok, fine, maybe I don't need generics in my application code as much as I thought, but their absence has cascading effects throughout the language and requires you to do ridiculous things all over the place. Not just the constant "if (err == null)" and interface{}, but also go-generate, which I have seen Go defenders seriously try to claim obviates the need for generics. What?!
If you have multiple things that can fail in sequence, you can "lift" into option and then the result of your expression will either be a real result or a short-circuited error.
https://drboolean.gitbooks.io/mostly-adequate-guide/content/...
Or just use exceptions. Either would be preferable to C-style error handling.
Hey, look, a monad! \o/
It also communicates exceptions via option types, and - by convention - functions that can go wrong return an instance of Result<SuccessType, ErrorType> initialized either with a success value or an error value.
There are some helper methods on Result. I like "expect", which panics if an error is indicated and returns the success value otherwise. The real boilerplate removal comes via try!, which returns the result (actually it re-wraps it in a new Result) if it's an error. When used on functions that can fail inside functions which return Result, the result is that try! bubbles the exception up the stack.
The experience is very like traditional exception handling, but you always know exactly how control can flow, since try! only returns from the function it is directly inside, and can only from functions which advertise that they can fail by having Result<> as their return type.
How is that different than with error return values?
> Or just use exceptions. Either would be preferable to C-style error handling.
I disagree. Exceptions seem to hide the problem. In Java and Python, I see people consistently fail to handle exceptions properly. The error paths are neglected because exceptions "hide" them. I would rather have a correct program with verbose error handling than a program that only has an elegant happy path but is otherwise incorrect.
Compare: https://gist.github.com/nickretallack/1462f2652cda6fc2a97283...
If you can find a way to improve these code samples, please suggest it.
> Exceptions seem to hide the problem.
I haven't used Java in a while, but I recall a feature where it would force you to handle exceptions. It knows what kind of exceptions a function can throw, and it can check if they are exhaustively handled throughout the program.
Specifically, I ran into this when I was using a database library. Values that can be converted to a database-friendly format implement sql.Valuer. However, the library functions just took interface{} instead of sql.Valuer because primitive types could not implement sql.Valuer. Can they implement it now?
Btw, you don't get a free pass at my other objections just because someone poked a hole in one of them. The other problems still stand. Take them one by one.
Let's say library A decides to add func (i int) MyMethod() and library B decides to add func (i int) MyMethod().
Then library C calls i.MyMethod(). Which MyMethod is executed?
You can come up with an answer to that question (e.g. "pick random") but there is no sensible answer.
To avoid this ambiguity you can only add methods to your own types but not someone else's (i.e. not to types defined in other packages or basic types).
> Btw, you don't get a free pass at my other objections just because someone poked a hole in one of them. The other problems still stand. Take them one by one.
You didn't mention other problems, you just whined about Go's feature set. You didn't support your opinions with anything debatable, so there's nothing to discuss.
> the language designers like to pretend it's not exception handling at all.
> You have to name things starting with a capital letter to make them public. Who came up with this convention? It is not shared by any language ever.
> There are built-in functions that use generics, but I'm not allowed to write one? Hypocrites.
func (i myInt) Foo() {}
Can you elaborate? A primitive value can easily implement an interface if you assign it additional meaning through a type:
# interface Stringer { String() string }
func foo(value fmt.Stringer) {
fmt.Println(value.String())
}
foo(1.0) # fails: int is missing method String()
type ValueMeters float64
func (v ValueMeters) String() string {
return fmt.Sprintf("%g meters", float64(v))
}
foo(ValueMeters(1.0)) # okay, prints "1.0 meters"Nothing in your comment indicates this is not still the case. You can't implement methods on primitive types, can you? That's a shame.
Think about it. How would you cleanly implement this? If you allow e.g.
func (x int) String() string { return strconv.Itoa(x) }
Then what does the compiler do when two modules declare that method? You could forbid it, but this will lead to situations where existing application code breaks because a library is updated and the new library version includes a method that was already implemented (possibly differently) by the application.The other option is to allow implicit casts such as
func foo(s Stringer) { fmt.Println(s.String()) }
foo(1) # implicitly casts to ValueMeters to satisfy Stringer interface
But then you run into the same problem. How would you select (forward-compatibly!) the right interface implementation to cast to?EDIT: The only solution that I can see is to allow function overloads:
func foo(s Stringer) { ... }
func foo(x int) { ... }
Without having thought it through very far, I think that this can be done consistently (precisely because all types are final in Go once the module is cmplete), but it would probably still lead to some surprising behavior.Actually, I just now see a problem with this:
func foo(x int) { ... }
func foo(x uint) { ... }
foo(0) # which one to use?
Literals don't have a type in Go until they're used in a place that requires a specific type. To make this example even more interesting, consider this: func foo(x int) uint { ... }
func foo(x uint) int { ... }
fmt.Printf("%t", foo(0)) # prints "uint" or "int", but which? x := 1.0
y := ValueMeters(1.0)
x + y // Mismatched types error - ValueMetres and float64
Because Go doesn't believe in operator overloading, there isn't even anything you can do about it. You have to abandon the pretence and explicitly convert the values to the same type.Go does a little magic with literals in that it will implicitly use explicit conversions on them to satisfy type constraints, so ValueMetres(1) + 1 works, but ValueMetres(1) + float64(1) does not. (Otherwise you end up with annoyances like not being able to add "1" to a float.)
This gets particularly gnarly when interacting with libraries which expect the underlying type you're wrapping:
x := ValueMetres(math.Sin(float64(x)))One of Go's best decisions ever IMO. To each their own :)
Or, give me a convention where public stuff looks normal and private stuff looks weird, because then at least my JSON serializer will work. In Python you lead with an underscore to denote privateness.
You could use interfaces to communicate privateness. If something isn't exposed in an interface, it can be assumed to be private.
Or just don't have your compiler enforce privateness. You don't need it. Plenty of languages get along fine without it.
I like post hoc polymorphism -- there's nothing more frustrating than knowing that a class has all the functionality required to meet an interface but because the author didn't declare it as such it's unusuable. Implicit interface implementation is great when you happen to implement the right method names with the right signature perhaps by luck, or you define the interface based on the implementation but this is backwards. The problem is that if you want to implement two interfaces that share a method name or you simply named a method wrong, you're out of luck.
Personally I think Haskell has the best implementation of this I've yet seen in a programming language.
1. Types (structs) are defined in one place and contain only data.
2. Typeclasses (interfaces) are defined in another place and define a collection of functions (methods) and values.
3. Functions and values are defined in another place and operate on types.
4. In yet another place you can arbitrarily pair any type with any typeclass by specifying the mapping of functions/values that you've implemented with those required by the typeclass.
All four of these can be defined in separate modules, giving you the ability to allow any type (even ones you didn't define) to implement any typeclass (again, even ones you didn't define) using any implementation (even ones you didn't define) -- the implementation definition is separate from all three. Because the functions/values in tyepclasses are namespaced, there's no fear of name collision. The implementation of a typeclass by a type creates a static vtable in the compiler only, so there's no runtime penalty or dynamic dispatch. I would kill for this level of flexibility in an OOP language.
Even the following is perfectly fine:
-- sum.hs
module Sum where
instance Monoid Int where
mappend = (+)
mempty = 0
ten :: Int
ten = mconcat [1..4]
-- product.hs
module Product where
instance Monoid Int where
mappend = (*)
mempty = 1
twentyfour :: Int
twentyfour = mconcat [1..4]
-- usage.hs
import Sum
import Product
twohundredforty :: Int
twohundredforty = ten * twentyfourEdited to add: Note that the above applies mostly to libraries. There's not really a downside to defining orphan instances in an application so long as you guarantee consistency (and the best way to do that, IME, is to put all your orphans in one place, with only that file marked -fno-warn-orphans).
First, the dynamics you describe mean I can always add (which is great! that's usually what I need to do to get my job done). They don't help much in reorganizing. If I come out with a new package that introduces a typeclass, I'll usually provide instances for common types. If, down the line, we want to move those instances next to their definitions, both my library and theirs need breaking changes.
Second, related, if I want to provide instances for types that are provided by heavy-weight dependencies, other packages that might want to depend on me would have to depend on those dependencies.
Mutatis mutandis for if I am introducing the types and the libraries have the typeclasses.
Peeling instances out into separate packages means a multiplication of packages or retains a coupling of dependencies, and raises maintainability concerns around orphan instances.
IIRC, Backpack might fix this.
With designing APIs, that reaction is used as a proxy for "doing it right."
You have to name things starting with a capital letter to make them public. Who came up with this convention? It is not shared by any language ever.
Some Smalltalks used this convention with Globals.
Actually, Microsoft created a languages like C#, F#, Visual Basic that promote -- though do not enforce -- this convention
> Do use Pascal casing for all public member, type, and namespace names consisting of multiple words.
https://msdn.microsoft.com/en-us/library/ms229043.aspx
C++ often has capitalization for methods (which often happen to be public) but not member (which often happen to be private).
Capitalization in Ruby denotes a constant, enforced by the runtime.
Java uses ALL_CAPS for public static constants and lowercase for private ones.
Capitalizing public things is not that weird.
I don't have a good feel for whether that's true of the Go community, or maybe more importantly whether it's relatively more true of the Go community than, e.g., the Python community.
I too am rather surprised to see Go, Lisp and Haskell cited as languages designed by people who are not dogmatic. This may well be true at the micro-level of decisions like "how do you best compile green threads", but at the higher design levels all three languages are very, very opinionated.
I think statement in particular is questionable:
Contrast that kind of discussion with the heated arguments or overly zealous statements you sometimes see from users of the same languages. There's a real disconnect, possibly because the users don't have the experience of weighing the arguments on both sides and don't realize how easily a particular decision might have gone the other way.
What is this implying? The designers of Go and Haskell never make "overly zealous statements" or "heated arguments"? Isn't the history of Go littered with such arguments, the most famous being that Go is simple because it's designed for Google engineers, who can't handle complex languages?
It's definitely true that language communities are often zealous: when library interop is bad they have to be, in order to get an ecosystem of libraries and tools. That's why you see people posting things like "I made a terminal emulator in Rust" to Hacker News but don't see people posting the same for C++ or Java. Those languages have established ecosystems already and need much less evangelism.
Haskell developers have a clear position of letting features get in the language as the community pushes for, and the overall guidance is very much open to changes. The one thing you won't hear there is any equivalent of "generics are bad for you, we are not adding them".
Monads I can grasp, but never got how they are like donuts.
http://dtrace.org/blogs/wesolows/2014/12/29/golang-is-trash/
I think he's just trying to say that people should be less tribal and dogmatic about their tools, ,and users should try to understand that language design is about trade offs, not holy war. I think it is unhelpful to lump rsc in with rob pike, they are very different personalities.
There were a lot of discussions on generics, and the language authors and contributors were certainly open to ideas and indeed did their own fair share of research. For example, see https://research.swtch.com/generic from 2009. Indeed, the FAQ entry itself on Generics states 'Generics may well be added at some point. We don't feel an urgency for them, although we understand some programmers do.', and looking at the blame log, that's from 2010 or earlier, and it's stuck to this day.
So, from everything I can see, the creators themselves are certainly open to the concept - I don't see any "you don't need generics" coming from that side.
https://github.com/golang/proposal/blob/master/design/15292-...
Only everybody who asks for generics knows that, and still wants them to go through and pay the price.
The "we'll only add them when there's a solution with no tradeoffs" is a red-herring, like "we'll add them on a day that doesn't end in -y".
>And I think most people will agree that Go has achieved a lot of success without them, so perhaps most use cases don't require generics after all.
In the same sense C has achieved quite a lot with buffer overflows, so what's the point in something like Rust?
And those people are outnumbered by people who are comfortable and productive with the current Go implementation.
Programming languages are not all placed on a single Bad<---->Good scale. There are many variables involved, and it makes sense to have local maxima like Java, Go, Clojure, C, Haskell, etc.
The only way to know that would be to be able to compare one implementation with and one without generics. But only one Go implementation exists. Perhaps there's some toy attempt by someone, but not anything that is equal in all other aspects to mainland Go but with Generics on top.
Plus, since most of the burden falls on the compiler writers and most of the benefits to end users, it just takes the compiler writers to be "comfortable with the current Go implementation" for this to continue to remain Generic-less.
1) tons of people have asked for Generics, few have asked for HKTs.
2) C++, C# and Java, programming languages with millions of users and extremely battle tested have Generics, no mainstream language at that level offers HKTs.
3) As a result of (2) a ton more people are familiar with Generics than HKTs, and so the latter are much less probable to be requested.
And of course, if they do add Generics, and people ask for HKTs, they should consider what they do about that then and there, not now.
No reason to never make a step towards somewhere just because you might (or will) be asked to also take a further one.
Except if they believe that they are already at some optimal point (which is my case I think).
All I'm saying is this doesn't seems like a simple equation to me. There are many possible solutions to the problem of building large, correct, maintainable, performant programs - and neither of the existing ones looks like a surefire winner given all the limitations.
https://golangnews.com/stories/1490-results-of-the-gopher-la...
Personally, I love most things about go, but its utter clumsiness at iteration and data transformation relegate it to a fairly limited number of use cases in my mind.
I do also really value the lack of change in the language though, it is important and rare that it is stable and not accreting features every year (compare with rust or c++).
In fact, because I used Clojure just before switching to Go, I found not having immutable variables harder to get into the habit of more than anything else. I actually first used Go because I needed to write something that manipulated bits in memory reliably, and couldn't be bothered going back to Java.
As a counter-example, I'm not sure this is true.
Java didn't have generics. Then they didn't have lambdas. Now they're even redoing the classpath! Have they gone nuts?
No, they've been evolving, and they've kept on adding features as it was opportune and useful to do so. If tomorrow's programming paradigms depend on HKTs and dependent types, then, yes, Go might want to add them. Today's programmers are just about starting to understand what a dependent type even is, so you're suggesting to include them in Go in order to ridicule the potential inclusion of generics. This is a fallacy, because generics and dependent types are years, decades, apart. And generics are very much part of how several generations of programmers have learned, and expect to be able to program.
If your language stops changing, your language is already dead. Yet another point of similarity between natural and programming languages.
As a aside, why not add HKTs straight with generics? The problem the Go implementors have with generics seem to be about run-time representation (boxing or unboxed), but no additional run-time issues arise from HKTs. The main issues of HKTs are that type inference becomes harder. Haskell's approach of eschewing type-inference at the kind level (because it's undeciable anyway) but giving unkinded type variables the kind * seems to be a run-away success.
Irrelevant. Go already has parametric functions like append, it just doesn't allow user defined ones. append isn't enough when a programmer needs to remembers 10+ tricks to make up for the lack of generics :
Citation needed. How do we know that the majority of Go users would prefer not to have generics? How do we know the number of non-Go users would be willing to use Go if it had generics is insignificant? Is there some large scale survey of Go users somewhere you can cite?
Of course they are. The people who are not comfortable with the current implementation aren't using Go.
It's a fallacy to only look at what the major users wants. It's similar to the “faster horses” fallacy.
Not true. What about those who write Go at work for instance?
Russ actually addressed this at length: https://news.ycombinator.com/item?id=9622417
But Generics are neither some new PL topic, nor have they not passed the "filter of practical experience" (millions use them in C++, Java and C# alone).
I'd rather they explicitly said: "It will take a total breakthrough in computer science regarding generics implementations for us to consider them for Go, and we wont ever care of paying the costs of any current, viable in practice for millions of programmers in other mainstream languages, approach".
Well I (somewhat) agree with that, but my take of it was Russ is basically saying existing approaches "don't work well with Go" and "recent ideas" are "not well understood" with the concern that "there's a princess in another castle after that one I am sure."
Or put it another way, per his emphasis on the "engineering" nature of Go, it is not an issue of tradeoffs, rather a concern that a runaway train of complexity will follow. Or if you will excuse my Mexican French, the problem is the fucken type system :)
A programming language design is the combination of a set of features. The hard part in designing a language is working out how all these features interact with each other.
For example, one feature of Go is that every type has a default value (0 for decimals, nil for pointers, empty string for strings, etc.). This property leads to the inability to include algebraic data types cleanly. To see this, consider this simple ADT (in Haskell syntax):
data Either a b = Left a | Right b
What's the default type here? It could be "Left(default-value-of-a)" or "Right(default-value-of-b)". You could introduce additional syntax to define the default value, but that increases complexity. You could introduce a rule how the default value is inferred in these cases, but that goes against the Rule of Least Surprise. You could remove the "default value for every type" feature that clashes with ADTs, but that causes problems elsewhere. Therefore Golang lacks ADTs and uses pointers and multiple return values instead.This is a dangerous and slippery slope to engage on.
Java was also extremely successful in 2004, before it supported generics. Yet you'd be hard pressed to find a Java developer who thinks that generics were not overwhelmingly beneficial to the language, and probably one of the main reasons why Java is even more dominant and powerful today than it was pre-generics.
I am not saying generic are not useful. But I have not found enormous use of Generics in typical Java business application. The most common generic data structure I have used in Java are Maps and lists which are already generic in Go.
Thanks to generics, millions of lines of code in libraries that you are using became safer and faster, and you are benefiting from this, even if it's not apparent to you.
Basically, generics made Java a better and safer language. Both theoretically and practically.
It's hard to argue that a feature that appeared a decade later changed the equation all that much. I like generics but would still use Java even if they did not exist. In my work the concurrency features are far more important.
Designers of Go have been disciplined enough to keep things simple - let's you keep more of the problem domain you're trying to solve, along with the code to solve it, in your head. Try that with a big project using another toolchain - lots of frameworks, lots of places to plug your code, with less coherency (in my experience) across the various experience levels of your team. And I'm talking about big teams here, not 10 devs.
Subjective Success metric with comparable teams: Go vs <x with generics>. If Go team gets solution done faster, correct, within budget compared to other team, your stakeholders will not give a crap about what devs think about generics. There's a good chance that would happen, based on my experience.
Java's current popularity is due to Android, not the bloated corpse of enterprise Java.
Android certainly made it more popular than it already was, but Java was extremely popular way before Android appeared.
If Java had generics from day one, they could have put methods like equals and hashCode in interfaces that some classes don't implement, instead of passing static type checks and then blowing up at runtime because you did something that never made sense.
Internally, the Go team has drawn up proposals for adding generics [1] but rejected them because of drawbacks. Hopefully one day we can see those drawbacks, because maybe a lot of us are more than happy to pay that price.
[1] https://www.reddit.com/r/golang/comments/46bd5h/ama_we_are_t...
Hopefully one day we can see those drawbacks
One of the main issues with Go is that we can't see those discussions. With C++, C, Rust, D, Java those proposals are public from Start to Finish.From the discussions I've seen on what Go considers complicated I'd bet it work out to be something like name mangling.
Go turns off ASLR for loaded C code because its easier to debug. Which is really only true if your debugging process requires memorizing the absolute address of symbols that control flow will jump too. I guess that is useful if you are reading the raw hex streams.
Name Mangling is complicating things because you can no-longer debug with ElfRead.
Go seems to reject anything that was invented after the ~70's. As those concepts are complicated.
I can't help but feel that Thompson and Pike just don't want to change the bad habits they've developed from by-gone ages and adopt modern tooling.
Why wait for "one day"? These proposals are public: https://github.com/golang/proposal/blob/master/design/15292-...
They should have thought about it at the very beginning of Go design. it's easy to say now "We didn't find a good way to implement generics" when their design choices made implementing generics insanely hard at first place.
Well actually,they thought about it, where does append,delete,make and co come from? how do these functions know their arguments and return types at compile time? generics.
Furthermore it's not all or nothing. Go could have supported parametric functions in user land just like append,make or delete without implementing full-on generics. They just don't want to bother with that, because it's impossible to retrofit generics now without breaking the reflect package. The rest is just excuses to evade the issue.
> Generics are not free
Creating a modern statically typed language WITHOUT generics isn't free either. Just like implicit interfaces are not free, just like the reflect package is not free, just like using interface{} somewhere isn't free, just like telling people to use code generators isn't free.
That is, a []Whatever is not as opaque as a Whatever{} or an interface{}. Same with a map[Foo]Bar, you (you being Go) know it's a map.
(I have no opinion on whether Go should have generics, I just think the apparent exceptions make sense even if they might seem unfair.)
https://github.com/golang/proposal/blob/master/design/15292-...
Go doesn't have generics because they don't want them.
https://research.swtch.com/generic https://news.ycombinator.com/item?id=9622417
Still, if you really feel passionately that Go with generics would be frickin' awesome, generics seem like a pretty easy thing to write a preprocessor for. With the great knowledge of programming language design and implementation that most pro-go-generics internet forum posters have, it should be a piece of cake.
I am basically doing this: https://github.com/lukechampine/ply
Ply is like Go except you can write things like []int{1,2,3}.filter(even) and it will Just Werk.
It's not quite ready yet, but it feels promising. The idea is that Go doesn't really need full support for generics; most people just want a few more higher-order functions available for manipulating slices, maps, and channels.
It's how C++ started. Unfortunately, that's how it will also end for Go, because of Go designers ignoring the past.
That said, I'm curious to know if Have is able to eliminate intermediate structures (deforesting) when chaining together transformations. Haskell performs "stream fusion" to accomplish this, but I'm not aware of how it's done in other languages. Ply does support this optimization, and that's mostly possible because it restricts the generics to a built-in set (rather than allowing the programmer to define their own generic functions).
My other goal with Ply is to make it as familiar as possible to Go programmers. It's not so much inventing a new language as it is scratching an itch.
Your second sentence answers your first. Not everyone likes writing the same code over and over again, even if it's simple code.
> generics seem like a pretty easy thing to write a preprocessor for
Very funny. But even if it were possible, the bigger problem with using a preprocessor is that everyone ends up using their own incompatible generics preprocessor.
Others have pointed out this is how we got C++ and stopped putting method dispatch and stack unwinding boilerplate at the same level as the code we really care about.
It definitely works out. There's just a question of whether it's an end result that we want.
There were many motivations for C++ -- and many more seemed to get added as it went along -- it's not just generics parametric polymorphism. Of course I would hate to say: Go should have every language feature or else we'll end up with Go++ or Objective-Go or something like that. But simplicity is not just a matter of not providing for something.
The general consensus from the Go developers is probably best summarized as "Generics are useful, but each of the implementation methods we have seen so far have significant tradeoffs, and the benefits are outweighed by the costs of the methods we've seen."
Which isn't to say that Go will never see generics, if someone finds the right way to implement them.
I think what people tend to disagree with is the last part. There is a lot of leeway in estimating the costs and benefits and people end up with very different estimates.
The truth is that one does not need generics. I've worked for decades in languages that don't have generics. The real question is with current software capabilities and requirements, is the convenience provided by adding generics to Go worth the cost of the added complexity. People clearly disagree, and the language designers decided "no". That's not dogma; that's engineering.
Sure ,when you can use interface {} AKA void pointers everywhere, you certainly don't need generics /s
You need generics if you want to avoid context.Context.Value(interface {})interface {} like API. You need generics if your goal is type safety AT COMPILE TIME. Obviously it's not one of the goals of Go /s ...
https://groups.google.com/forum/#!msg/golang-nuts/Rj5t1h_ztx...
Sure, the final implementation still has a couple instances of interface{}, but they are much more innocuous than your example.
The net/context package was designed to pass request lifetime context throughout your service infrastructure (think microservices), where the values provide metadata from your transport protocol. HTTP headers, if we want to think in terms of HTTP, although obviously there is no specific protocol dependency at this layer. The value may not even be assigned by a Go program, so it seems that runtime validation would be a necessity even with generics. A broken or malicious program upstream could easily inject something into the context that you weren't expecting, or simply not supply the value at all.
So, if you want to use types to make Context safer to use - and I'm not saying this is necessarily desirable, but assuming it is - you could start with something that works in the existing Go language: explicitly marking what kinds of objects can be keys rather than using interface{}.
type Key interface { _dummy_key_method() }
type Context interface {
...
func Value(key Key) interface{}
}
func WithValue(parent Context, key Key, val interface{}) Context
(Incidentally, I find it odd that WithValue is a freestanding function while Value is a method; I suppose this is to reduce the burden of reimplementing the Context interface for a new type, but is there any actual reason to want to do that?)This helps prevent you from accidentally using the wrong object as a key, a bug that might be tricky to catch if the key you meant to use is legitimately sometimes not present in the Context (in which case your code wouldn't error out due to nonpresence of the wrong key). It also prevents hacks like using strings as keys in lieu of declaring dedicated constants, which would create the risk of silent failure due to typos. (It seems this is already considered a bad practice, but such a signature would enforce it.)
Once you do that, it's apparent how generics could further improve safety. Using imaginary Go syntax:
type Key<T> interface { _dummy_key_method() }
type Context interface {
...
Value<T>(key Key<T>) *T
}
func WithValue<T>(parent Context, key Key<T>, val *T) Context
Now you're safe from type mismatches. Actually, it seems that best practice is already for packages to provide type safety by defining wrapper functions, such as this from userip: func NewContext(ctx context.Context, userIP net.IP) context.Context
func FromContext(ctx context.Context) (net.IP, bool)
That works well enough, but generics save you the boilerplate; packages could just expose the keys as public constants while retaining type safety. Admittedly, those two functions are only a few lines to define, so there's not that much boilerplate to save, but it's something. Generics would also make it possible to write type-safe generic helper functions involving contexts, though tbh I can't think of many obvious ones you'd want. Maybe a function that panics if it can't find the key: func MustHaveValue<T>(ctx Context, key Key<T>) T
...though that would be better handled by a proper Option<T> type anyway. But that too would be enabled by generics!Okay, but there's still more that the compiler could guarantee. The value is now guaranteed to have the right type if it's there, but for all we know it could be missing. Indeed, this is probably a bigger footgun than types are in practice: it's easy to accidentally have some code path that bypasses the code responsible for setting a particular key. Unfortunately, this is a harder problem to solve even with generics, and in fact multiple Rust web frameworks have run into very similar issues:
https://github.com/nickel-org/nickel.rs/issues/79
https://www.reddit.com/r/rust/comments/2cl7bw/web_frameworks...
Arguably what you really want here is extensible records with a structural type system. For example, in Elm, you can have the expression
{ foo = "1", bar = "2" }
and it'll be of type { foo : String, bar : String }
But then, given any record type, you can add more stuff onto it (I think this works but haven't tested it): type alias HasFoo a = { a | foo : String }
addFoo : a -> HasFoo a
addFoo rec = { rec | foo = "1" }
With a feature like that, your functions' context parameters, instead of having the plain Context type, could explicitly specify that they need a Context with a given key. Thanks to generics, you could call functions that add new keys without losing track of the keys already in the map; for example, with the above Elm code, if you called addFoo { bar = "2" }
the result type would be { bar : String, foo : String }
In fact, it is possible to simulate this in languages that lack native support for extensible records but do have sophisticated enough type systems - anything from C++ to Rust to Haskell. I think the Iron web framework for Rust does that now, though I only took a quick glance at the docs. I'm not sure exactly what this would look like in Go, both because the implementations tend to get pretty ugly/hacky, and because a hypothetical generics feature for Go might not add enough power to make this expressible. (Which, to be clear, isn't necessarily a bad thing; every bit of complexity has a price, and C++ templates in particular are notoriously awful to read and debug.)The fact that interface code often has runtime failures is usually due to a feature Go interfaces get you that many languages with generics don't have in the first place -- the ability to downcast to concrete types.
Now, interfaces sadly do not have the same amount of type safety at compile time for defining new containers and you can't do things like return value polymorphism as well, but it's not that bad.
It requires a different style of programming (the post from rsc linked in the sibling comment is particularly good at illustrating this), but it's not that bad.
I say this as a Rust programmer (also a Go programmer, but primarily a Rust programmer) who absolutely loves generics.
you're assuming you know the kind of problems I'm trying to solve everyday. You don't. Don't patronize me.
I write a lot of algorithms that can work with float, double, complex float, and complex double. Without generics/templates (or a macro pre-processor), I have to cut and paste that code 4 times, or I have to sacrifice performance. Maybe I don't need templates, but I really really want them.
Just to be clear, I personally dislike tabs and line-lengths over 80 characters. However, such choices have no bearing on the success or failure of any project I have ever experienced.
Cross-project style consistency doesn't buy us anything reasonable--there's no "engineering" about it.
Also, braces around conditional logic isn't what I'm talking about here (since that's not a "go fmt" style choice--that's the language syntax).
Most project teams have limited resources to come up with a solution that is close enough to correct to make the project pay off. In new technology there also tends to be a small set of technical issues that are hard and that you must get right--nothing else matters that much if you fail on the core. I am pretty heartless about anticipating things like code conventions, security requirements, legal review, etc. and just following the rules or otherwise making them non-issues for the project team in areas where it does not fundamentally alter the outcome.
Edit: clarification
Their argument has never been that you don't need generics. It has been we don't want to add them in the currently available forms right now and here are the ways you can get around their absence in the meantime.
However, the Go team has gone further than most in their rejection of changes, and I think it's reached the point where it hurts the adoption of the language. When I write Go code, I feel like Rob Pike is standing behind me telling me to do things the way he likes them done, or not at all.
There are plenty of examples of languages that have navigated this problem better than Go. Rust and Haskell come to mind immediately. C# and F# should also get more credit than they do for handling this kind of thing gracefully. Even Java 8 has incorporated many of the things people found attractive about Scala, without making them mandatory or alienating established developers.
(Specifically I'm thinking of things like the problems of doing builds of Kafka across different Scala versions. This was a couple years ago so maybe things are better.)
Ultimately, people will write a Go++ because the language isn't good enough. That's not avoiding C++, that's forcing people to recreate C++ .
But then you still have to follow the rules of the UX, consistency, flexibility, all that, and none of the languages I know of are serious about that. It's all just dogma everywhere, or "engineering trade offs" if you like. We don't have much dogma only when it comes to performance, since it's the only thing easily measurable.
If I want a simple language, no generics I go with Go, for everything else I go with Rust and Swift.
Not every language has to have generics, when I want to use them, I simply switch languages.
There is something nice about being able to keep Go in your head and I think that it has shown that there is a market for such a language.
Python,Ruby and Javascript don't have generics ... because they are dynamically typed. Go has generics, maps and array are container types, you just can't define your own. To say Go has no generics is stupid, it just doesn't allow you to define your own. Instead you can use catch all interface{} everywhere like dynamically typed languages.
> Not every language has to have generics, when I want to use them, I simply switch languages.
This statement makes no sense at all.
That's usually what is indeed meant by the statement "Go doesn't have generics", maybe it should be "Go doesn't have user-defined generics", but I assume most people can infer that. I really hate the recent trend of having to spell everything out.
> This statement makes no sense at all.
Can you be more specific? What doesn't make sense at all in saying that when I have a simple problem that is unlikely to benefit greatly from a more sophisticated type system, I reach for Go, otherwise I use a different language?
I think it's too easy for community members to leave opinionated outsiders with a bad taste in their mouth when they approach your system with opinions and tendencies that were built up in other systems with different circumstances and assumptions. The kneejerk response is "well, you're wrong, we don't do it that way here," hence the dogma.
It takes serious effort to fight that instinct, and not everyone is good at fighting it... especially when some community members suffer from the same problem as the outsider, but with the affinity being toward their own system. This is a hard problem to solve...
It's a good thing this article was written and there is a willingness to at least acknowledge the problem in the Go community. It contrasts to the style of say, Linus Torvalds, whose approach is basically "empathy? fuck that, I'd rather call them names" :-)
There Is More Than One Way To Do It.
Regardless of the community, we generally all strive for the most efficient way to accomplish a goal, but the definition of "efficient" can vary: execution performance, elegance, ease of developing, ease of integrating into X... Often, one or a few leading solutions emerge but it's tempting to take those pragmatic optimums and turn them into law—or worse: follow them for the wrong reasons.
It's a good idea to know why you're using a particular design tradeoff, and remain flexible to change if you find your constraints have changed. Some environments make this interchangeability easier than others but there's almost always a choice.
That's a very generous definition of similarities, as tons of languages have green threads, even Java/JVM had them initially (and still has them in various implementations over native threads). Could even add that most languages also have GC, some type system, io, modules, etc.
compiles to machine code
have green threads
use non-blocking IO in the background
have parallel runtime
Haskell is the first that comes to my mind. (I guess that Common Lisp is a safe bet too, as it had everything for ages before it was cool;), but I'm not familiar with that domain to be sure) Erlang have everything except machine code compilation.
What else? I guess the rest is from the newer, lesser known or experimental languages. So is there anything more popular than Haskell?
I think Julia is there too, not sure.
- compiles to machine code, check. A whole operating was written in it, AOS (aka BlueBottle).
- have green threads, check. That is where the name Active comes from. It makes use of tasks.
- use non-blocking IO in the background, check. Tasks are used everywhere across whole OS.
- have parallel runtime, check. The whole OS makes use of parallelism and its compiler, Paco, does compilation of modules in parallel
https://en.wikipedia.org/wiki/Active_Oberon
http://www.ocp.inf.ethz.ch/wiki/Documentation/Front
http://www.progtools.org/article.php?name=oberon§ion=com...
I majored in systems and distributed programming, and had the luck of having a set of teachers that cared we should know the history of computing, making us curious about our past and how those designs could influence current ones.
So the habit of hunting and reading papers stayed with me, even if during the day I just do boring enterprise consulting.
Many times they are more entertaining than reading newspapers on the daily commute.
compiles to machine code: yes
have green threads: I think DispatchQueues should qualify for this
use non-blocking IO in background: yes
have parallel runtime: I am not sure
A way to fight this is to make sure you belong to more than one language community. Then you'll be forced to not make such a simplified model.
"Go" is such a poor name, both for a language and game, because they collide with the English word, it's impossible to perform targeted search or sometimes even to know what an article is about.
Some TV shows like "24" suffered from the same fate. On reddit the game of Go is represented by the Korean "baduk", probably to escape the confusion cone.
("Golang" is a lot less problematic, of course. Hell, in an alternate universe, why not "Gol"? Only one more letter, and no serious collisions I can see...)
Except millions of sports-related material, the brazilian airlines and so on and so forth :)
Unless you're writing in Perl or javascript, almost every programming that exists is either a short English word, a person's name, or just a letter with maybe some punctuation.
There's also the fact that (at least) Google uses your private information, global search trends and whatever else to try to make a better guess of what you mean. In my case, just writing "Go" gives me back golang.org as the first result.
It's more unusual for a programming language to achieve success WITHOUT a naming collision with some common English word or proper name.