It feels like it was invented in a universe where Haskell, OCaml, Erlang, Smalltalk, Lisp and so many more languages and research in languages never happened.
It feels like it was invented in a universe where Haskell, OCaml, Erlang, Smalltalk, Lisp and so many more languages and research in languages never happened.
You can pick it up in a weekend. A lower entry bar means more people will try it out.
> It feels like it was invented in a universe where Haskell, OCaml, Erlang, Smalltalk, Lisp and so many more languages and research in languages never happened.
It was developed in a large enterprise context not in an academic context. I think it shows.
I know it sounds like a lame reason to dislike a language, but I've always found staring at Lisp to be so much more difficult and distracting than C-style syntax.
https://en.wikipedia.org/wiki/Dylan_(programming_language)
Also, Julia was syntactic and/or semantic sugar on top of femtolisp. Got converted to it in first pass with LISP's power doing the rest. Here's Stefan Karpinski on that:
"So ultimately the reasons for Femtolisp are:
1. Scheme is excellent for writing parsers since trees (aka S-expressions) are its forte.
2. Femtolisp is a small, simple, highly embeddable and remarkably fast Scheme.
3. We control it (and by "we" I mean Jeff) and can fix any bugs we encounter."
Yep, No 1 are those godawful parentheses and s-expressions making the job easier. ;)
And then you discover paredit and you start wishing every other language would let you treat your source code like that.
It's just never been a go to language for me.
printf("Hello world");
Lisp mode, move left parenthesis, remove semicolon (printf "Hello world")
Second round if (var == 2) {
do_something1 ();
} else {
do_something2 ();
}
Lisp mode, move left parenthesis, remove semicolon and curly brackets (if (= var 2)
(do_something1)
(do_something2)
)
On a big source file I am not sure if the amount of parenthesis is bigger than parenthesis , bracket, curly brackets and semicolons counted together.The latter group tend to find lisp not that painful because lisp parens/s-exps are as explicit as you can get, and indentation makes parens almost invisible
Which makes Go's rapid popularity as a language for solving the same problem even more peculiar. Especially since Go's surrounding tracing, debugging, and online code swapping facilities are so much worse.
I'm sure some component of that is Ericsson's complete lack of interest in evangelism and Erlang Solution's apparent lack of capability to effectively evangelize, but it still seems like Go isn't so much filling a void as it is being better at attracting an ecosystem that delights in reinventing a particular set of wheels.
Java was very much the same way for a very long time.
Session establishment for PSTN can be relatively expensive (much in the way of TLS handshakes), so concurrency and shared-nothing memory model together allowed for real-time streaming to keep on working no matter what else happened. The three main features of a PSTN switch are, after all:
1. Reliable call switching
2. Reliable real-time throughput
3. Reliable billing and accounting data generation
We don't think much of throughput these days, when any home office switch has gigabit ports and 40Gb+ backplane. As far as I know, maximising throughput bandwidth was not a primary consideration with Erlang. Reliable real-time streaming is much more about guaranteed latency - and incidentally, optimising between latency and throughput tends to be all about tradeoffs.
* Inasmuch as you can make a blanket statement like this about a language, Go's speed is very much comparable to Java's.
* Java has a huge number of garbage collectors available. You're just talking about the default, but there are extremely low-latency GCs.
* Java has a bunch of ahead-of-time compilers; thanks to Android this might be the most common deployment of Java.
Even green threads were tried (and sensibly abandoned) in Java before.
I think people may not be aware of all the options available in the Java ecosystem, but other than sized types, which are theoretically coming to Java 10, there isn't much performance-wise that Go does and Java doesn't.
1x time: C
1.5x time: C++ (with smart pointers)
~2x time: Objective-C (almost everything is a heap smart pointer)
3x time: Java, Golang (optimized GC languages)
I'm probably outing myself as the lord regent of all impostor plebs, but Erlang is not IMO an approachable language regardless of its origins. Put differently : it looks completely nuts. I'm sure there's a method to the madness, but comparing it to Golang feels misguided (at least, from the perspective of awful programmers like myself)
Elixir by comparison is significantly more complicated, but the fact that it looks tacitly like Ruby is apparently very attractive.
I suspect I could sit down with you for an hour and remove all the weirdest seeming bits which feel alien. Such that the rest of it would just feel like any other programming language, and you'd be able to immediately start dissecting and understanding other people's code.
Part of the problem with Erlang (again an evangelism problem) is that there's not much in the way of attractive and we'll structured on-boarding documentation. It's mostly really simple. It's just that the Erlang docs seem to go to great lengths to obfuscate that fact. In large part because they don't do anything to intentionally build "background schema" with the reader by drawing comparisons to similar things they're probably already familiar with in other ecosystems.
{ok, u_no_me, {down_with, init, [State, Mod, OtherMod, Pid, SomeImportantRefIforget]}, {permanent, brutal_kill, 10000, 9999, 100000, 5, worker}
Is not a genius language construct of a faraway Alien race. It's not elite. It's not even Swedish. It's just limited and unclear. (there are even more unclear examples in the annals of Erlang, but everyone is familiar with consulting and reconsulting the child specification documentation)There are just so many things that make dealing with OTP nicer in Elixir. This is to say nothing of the meta-patterns that Elixir has driven home: Agents, Tasks, the actual supervisor/worker interfaces themselves. You could go on here.
I haven't even gotten to macros -- Have you ever had to make your own behavior, say for an acceptor/worker pool of some variety (presumably non-tcp, otherwise just ranch that out). Making your own behavior (what armstrong himself called "really advanced") is the easy part here. You've got to create a system where you pass around the TRUE module that holds the correct `handle_call/cast/info` implementation back and forth through the true module and the OTP state parameter.
This whole pattern (which comes with very real runtime costs) just doesn't even exist in Elixir. You just create a macro. A macro that sets all of this up at build time. No complex supervisor hierarchies where you need to constantly inspect each and every message, no custom behaviors, etc. You just have a mostly sane way of sharing software logic to begin with.
You can work up really clever solutions with parse_transforms, even standard "-define", but today Elixir has a totally brilliant solution to this problem and many more classes of problems.
This isn't to say there an't blindspots (beyond "wrap proc_lib/inets/other hated library"). There's minor issues here and there. Process registry naming can get confusing, especially operating between Erlang+Elixir supervision trees. Low-level details aren't nearly as well-publicized in Elixir either. There are even big issues, like the Elixir community leaning more "I'm playing around with Elixir because Phoenix is Webscale" than the grizzled realtime adtech/gambling/finance gurus and so if you want help with OTP platform/VM details, you going to have a harder time.
I first tried Elixir in v0.08. & function capture syntax had just been standardized. I suspect that like me, you tried it then, when it wasn't a fully-featured alternative yet. Things have changed and Jose Valim has a vision for the future of Erlang that I think you'd be foolish not to pay attention to.
Having used both Erlang and Elixir "in anger" there are a whole bunch of things that bug me about Erlang. There are a whole bunch of things that bug me about Elixir too. I'm not sure what any of that has to do with what I said. Elixir is a more complicated and larger language than Erlang.
It has structs (which are weirdly implemented as maps instead of records). It has type-classes/traits by way of "protocols" (which are a whole different layer of metaprogramming that could be replaced with behaviours and data-structure definition convention or dialyzer type-spec'ing). It has the pipe operator (which strangely elides the first argument in a function such that map/2 becomes map/1 when written... why no placeholder sigil). It has Agents (which are something which is confusing to have in the standard library as its a specific implementation of a gen_server which acts as a KV-store which will be really, really prone to data races). It has a fair bit of metaprogramming to be aware of in terms of macros and hooks that trigger at code loading/import time (which forces one to need to be a lot more aware of the inner details of 3rd party libraries being used). It eventually devolves into requiring knowledge of Erlang when attempting to do anything regarding debugging, tracing, or distribution related.
Elixir is a great language, and it has a lot of neat features, and I love teaching it to people and being increasingly critical of Erlang based on advancements in usability and community engagement/feedback that I see happening around Elixir. But I don't really need to be proselytized to about it.
> You can pick it up in a weekend. A lower entry bar means more people will try it out.
... and then you get the JavaScript situation where everyone thinks they're an actual programmer at $TEAM_LEAD level of sophistication when it comes to modeling, design, architecture, testing and implementation... but they're really not. It's not their fault per se, it's just that they do not have the necessary experience to realize the areas that they're lacking in. (This is a well-known cognitive bias: More than 50% of people think that they're an above-average driver. There are a precious few who realize that they're not -- they are the exception.)
Programming is hard[1] and if there was an instant-humbling device, I'd buy it in spades.
[1] Most people focus on the "oh, that's Undefined Behavior" bits of it, but it's not really about that. It's about recognizing the types of mistakes that you make and working to avoid them or creating a system that does, whether that's by testing or proof or whatever. Obviously it still has to be _practical_, but AGAIN... it's about tradeoffs. If you don't care about correctness, I'm pretty sure I can whip up a "solution" to any problem that's fast as hell... and not correct. Summa summarum: I think we need to be thinking a lot more in terms of tradeoffs and not so much in absolutes.
I've seen a lot of people in academia writing C++, mostly horrible code. I've seen electrical engineers writing awful assembly code. Because the incentive is mostly to get one task done today. None of these languages have a lower entry bar.
JavaScript is pretty much bound to a singular use case (web development) where there is a lot of incentive to get quick money, which attracts all kinds of people. I don't see the status quo you're referring would be much different if the browser scripting language was Haskell or brainfuck.
I agree, but there are a lot of jobs where it's actually effectively impossible to distinguish between good vs. bad practitioners of said jobs.
I just have this feeling that it should be possible in programming. (Because it's quantifiable... or at least quantizable into "works" or "doesn't work" along any number of axes.)
> I've seen a lot of people in academia writing C++, mostly horrible code. I've seen electrical engineers writing awful assembly code. Because the incentive is mostly to get one task done today. None of these languages have a lower entry bar.
Same here, only with FORTRAN and C.
(This hints that this may be a meta-problem.)
> JavaScript is pretty much bound to a singular use case (web development) where there is a lot of incentive to get quick money, which attracts all kinds of people. I don't see the status quo you're referring would be much different if the browser scripting language was Haskell or brainfuck.
Well, there is node.js, but really, the problem with JS is... JS. It's just a horrific language semantics-wise (syntax: meh). It was invented and implemented in ~7 days(?) and it really shows. (No blame towards Brendan Eich, he did the best that he could within the deadline and even got a little bit of higher-order programming in there.) It's been improving, but just imagine the burden of improving a language that's already been deployed to 1B+ computers. Not an enviable task.
Hopefully WebAssembly will (in time) address these problems and give us a real way to program the front end in $WHATEVER_LANGUAGE_YOU_WANT
I think Pike and the other designers skew more towards corporate research (Bell Labs). And surely the development of GO as well as other Google research projects are intended to win in the market place.
From https://talks.golang.org/2012/splash.article > The Go programming language was conceived in late 2007 as an answer to some of the problems we were seeing developing software infrastructure at Google
One of my professors used to tell us, that C was built by people who wanted to use it and didn't care about academic style.
In many ways Go is just the next step of C. C did not have object orientation and even passing functions around was kinda hard. While C++ tried to bring object orientation to C (total failure) Go decided to keep the core values of C and instead improved the rough features (e.g. easier binding of functions to structures, faster build times).
By making it easier to pass around functions Go enables functional programming styles, but at its core, it is still just an improved C. The only revolution within Go (as a language) is the concurrency and channel concept and I think that was taken from some functional language (not sure).
I really like Go, because it just feels right. It might not be as clean as Smalltalk or Lisp, but it has data structures and functions, teaches you how important interfaces are and lets you build highly concurrent applications with ease. In addition, it brings a nice set of tools which integrate well into a shell driven workflow.
After all, the whole thing should not surprise anybody as Ken Thompson[1] was part of the Team which invented Go.
I'm not a Go programmer, but I have a lot of respect for it.
If you're wondering "why Go"? Think of it as a modern version of C, at a slightly higher level, developed by the same people for slightly higher-level tasks. They made C for low-level stuff, and then picked up with Go for higher-level stuff. It's like C+.
C has been very successful in part because it's so simple in certain respects (although not in others) and I think Go will be successful for many of the same reasons. Go does what it does very well.
I think Rust is actually a great choice for something like Tor, but I wouldn't use Rust for some of the things I'd use Go for.
That's perfect.
The one improvement over C related to build time in Go is that you don't need include files. Granted, that's a big one. But modern C compilers make header parsing extremely fast.
[0] https://en.wikipedia.org/wiki/Communicating_sequential_proce...
I think we've seen a few posts like that on HN and now I wonder, what that means exactly. What else falls under the "feels right" umbrella for you?
- In Scheme/Lisp, you write the function name before the opening parenthesis. In languages related to C, you write the function name before. For me, the second one feels better. In general, I like the C syntax pretty much.
- In C++ you easily provoke very long compiler error messages. Go compiler messages are much shorter and much more to the point. I like the ones where I do not have to search the real error in the error messages themselves. (I heard Rust compiler error messages are even better).
- To run a go program on a computer you just need the binary, done. To compile a program you need the compiler which comes with a few cli tools, done. For java, you have to decide if you need the JRE or JDK, agree to some license before being allowed to download it from their website. In addition, you have to place the jre on every computer which should run your program. I like the simple way.
- When I design a program there are a few parts. One is the Entity Relationship Model. Sometimes I think about it as something that has to be saved to an SQL database, sometimes as an object oriented inheritance hierarchy. For both there are reasons, but even simpler is it to just think about it as a struct or JSON. This fits pretty good for designing JS and go apps.
- When I write bash script I know what a slow language feels like. I know there are faster languages than go (e.g. fortran), but I am also aware that much of the performance is in the hand of the developer (e.g. memory management). When I write go programs I feel like my efforts to make the program efficient are worth my time and the tools support me while doing so.
- Last but not least go supports then functional programming style when needed. I like that. Sometimes I miss object orientation a little, but that mostly happens when I forget what interfaces are for. And while I truly respect Alan Kay I think object orientation has been overused/misused enough, so that its ok for me, that go didn't build it into the language.
So maybe I just like 'simple'. I am sure, that for every example I listed you can find a language which does even better than go, but I think it becomes harder when you consider all examples together. Nonetheless, the list is far from complete, I just tried to write down out of my head, what I like about go.
after
> the second one feels better
Not really. Parentheses makes code manipulation easy. Thus it actually it FEELS better, though it may not LOOK better.
Nevertheless, please elaborate. What do YOU think is the difference here between feeling and looking? Do you mean that your favorite editor (or the majority of editors) better supports outside parenthesis?
If the cursor is here
(foo bar)
^
I can cut the whole thing using just d%: combine d)eletion with the % cursor movement (jump to opposite parenthesis). Then you can paste it elsewhere with p.It's a POSIX-standard feature: see here: http://pubs.opengroup.org/onlinepubs/9699919799/utilities/vi...
That's just a crap editor for sysadmin tasks I wouldn't use for development.
If the syntax is:
foo(bar)
^
then we jump only over the argument list, not the whole expression.Another thing is the damned comma disesase in f(x, y) languages. Say we are in Vi:
(foo abc def ghi)
^
We want to swap the last two parameters. Easy: type deep. Done! d)elete to e)nd of word, go to e)nd of word, p)aste.Now try it with
foo(abc, def, ghi)
^
Annoying!Imagine if your operating system shell forced you to use commas between command line arguments. Nobody would use such an idiotic thing. Why do we put up with languages that do that?
$ ls, -l, *.foo # just kill me now
Move second argument to third position: "parens outside" together with "no commas between arguments" makes it a breeze: (foo (a b c) (d e f) (g h i) (j k))
^
Instead of deep we just do d%%p. Done. (foo (a b c) (g h i) (j k))
^ d%
(foo (a b c) (g h i) (j k))
^ d%%
(foo (a b c) (g h i) (d e f) (j k))
^ d%%pI think I will still prefer the C style for parenthesis (as I would care more for the LOOK than for the FEEL ;-), but regarding the commas, I agree that skipping them would make the life a easier. I mean besides lisp and bash/shell other languages have done so too (e.g. Smalltalk) and spaces aren't allowed anyway inside parameter names.
Yes.
> Nevertheless, please elaborate. What do YOU think is the difference here between feeling and looking? Do you mean that your favorite editor (or the majority of editors) better supports outside parenthesis?
Lisp has a two-level syntax. The first level is the syntax of s-expressions. On top of s-expressions we have the actual Lisp syntax.
S-expressions have a few features:
* it's a data syntax for lists/trees, numbers, characters, symbols, strings, ...
* delimiter surround the data, it is always clear where the expression begins and where it ends
* whitespace is used to delimit the elements
* s-expressions are not sensitive to lines and whitespace
* s-expressions can be automatically formatted by simple rules, according to different widths
* the tree structure is explicit, not implicit. It is visible, based on the s-expression nesting.
For an s-expression editor it makes not much difference to edit a data list like ((berlin germany) (rome italy) (paris france)) or code like (defun collide (object wall) ...) .The first level of editor support you get for editing s-expressions.
Thus editing on this level FEELS like you manipulate data: create, transpose, delete, list, de-list, flatten, copy, indent, format, ...
That every list has explicit delimiters makes clear where the expression begins, where it ends and what its contents are. The parentheses also serve as 'handles' for the thing. If you use some more advanced Lisp system, the s-expression creates a region and moving the cursor into this region enables context sensitive commands. This is possible in other systems, too. But here the relationship between the s-expression and the region is visually clear: each expression has explicit delimiters, front and end.
So, the first level of Lisp editing is data manipulation. That's a big difference to editing many other languages, where your program is not also a simple data-structure. There you are always on a language level, maybe on a primitive token-scanner level. You can reconstruct the tree structure, but it is not visible, explicit and delimiting like in Lisp. If you refactor a program, you work on the programming level - in Lisp you can work on a plain data level, too. This makes code and data interchangeable and when you work with a Lisp listener (running a read eval print loop), you will work with code as data and the listener helps you: you get support on the language level & the s-expression level on the editor side. But at the same time you can cross the the border into the programming language: you can let Lisp manipulate your program. Thus programming becomes a mix of manipulating text and data. The s-expression syntax helps to make that simple - because of the features above.
A typical example would be writing a macro (Lisp code which transforms code) based on some existing expressions. You would take the expressions, convert them into data, create the transformation code, define the macro. Then you would test the code generator. Thus suddenly from writing code, you switch to writing code-writing-code and the input and output is no longer data, but code as data. Thus while programming you will interact with the code generator. This can be done in many languages, but in Lisp it FEELS different, because you work on s-expressions - easily delimited hierarchical pieces of code as data, which can be transformed by your editor and your underlying Lisp system.
After a while, editing conventional code FEELS less direct. It feels like you manipulate the code with instruments, while a good Lisp system feels direct. That's it direct manipulation of code and data.
A Lisp programming will learn this code manipulation side and then is willing to give up some looks for that. Originally Lisp had a more traditional surface syntax, but it turned out to be more practical to use the s-expression based code representation not only internally, but also using it externally on the display or textual side.
Some time ago I used DrScheme (now Racket) to write Scheme but I never found it to fit easily into my workflow/use-cases.
So I would I like to use the language with the following workflow (100% terminal):
- write code with vim: vim main.lisp - simply compile the code to binary: lisp build main.lisp - execute the compiled program: ./main
Any ideas?
Essentially for anything slightly complex one would use an interactive programming style and 'only' deliver the application in a batch style.
Racket is more oriented towards batch programming, compared to popular Common Lisp development environments.
You can edit a Lisp program strictly in files, and run a build step to produce a clean image which is tested, using a REPL just as a debugger, to "go in" and find out what is wrong.
Still, Lisp is good for that. I've debugged Lisp programs with print statements and it was at least as good an experience as debugging programs in other languages using print statements.
For instance, if we compare to C, C has no trace, and generally no easy way to wrap any function with a wrapper that takes the arguments. Ecosystems built around C, like the Linux kernel, have developed things like that: Linux has a function tracing thing in it (more than one, I think).
Too many features.
Unless the project is very badly configured, that should be all you need to compile it. Now, writing those .gradle files...
Compiling most Java projects is usually as simple as 'brew install maven; mvn package'. Maven does require a build file, but it can also do more than the go utility, such as deploying artifacts in a repository, build distributable tarballs, RPMs, debs, etc.
Maven, like go, largely relies on convention over configuration as well. If you generate a POM file from the standard archetype, you basically drop your files in the right source directory and it will build.
(I am not a fan of Java, but I think a lot of the criticism here is lazy. The first time you use the 'go' tool you have to learn its usage as well: how to separate library code from programs, what are the conventions for package structure, how to avoid API breakage for downstream users, etc.)
Gradle build files have confusing syntax. Just knowing which lines have an equals sign and which don't is a bit of work.
Sure, I could use something like Haskell, but then I'd have to worry about whether I'm accumulating a giant stack of thunks that'll blow up. go just works, and less of it will bit-rot than the comparable python script, thanks to at least some types.
This is why the SREs seem to be fans, e.g. https://talks.golang.org/2013/go-sreops.slide#1
I wrote some machine learning tools in Go and the experience is quite bad. The lack of operator overloading and parametric polymorphism make most ML code ugly. It also does not help that Go's compiler backend does not optimize very strongly and calling out to C comes with a relatively large overhead.
Sure, I could use something like Haskell, but then I'd have to worry about whether I'm accumulating a giant stack of thunks that'll blow up.
There are many languages between Go and Haskell that are productive and provide a sufficiently strong type system.
At a previous gig, I had done a lot of F#, and I think that's close to my personal sweet spot, but I'd have to use it frequently to keep it in my head. Go is small enough that I can load it into cache when I need it.
Definitely! I have continued to use Go for small utility every now and then as well. The standard library is extremely well-suited for that kind of work.
(Now using Rust more in that role as well, but mostly to get continued practice ;).)
So you use a language that makes specifying ownership (and immutability hard)? I always feel Go adds a lot of mental overhead.
E.g., if you have a method that returns, say, a float32 slice. 1. If I return a slice of an array/slice that is a struct member, the caller could modify elements in the slice, breaking struct invariants. 2. Returning a copy of the slice is safe, but adds a lot of overhead. What you'd actually want is to return an immutable slice, but Go does not provide any facilities to do so (apart from wrapping a slice, but the lack of generics and operator overloading makes this tedious).
I guess a lot of Go code will just assume that returned pointers/slices/maps will not be used in a way that breaks invariants. But you usually end up reading the source code of 3rd party packages to see what is safe, whereas in other languages you could just read the method signature.
tl;dr: I think ownership and preserving invariants usually give the most mental overhead and Go does zero in that department.
The funny thing about Go is that it makes up for its long list of weaknesses with essentially one single strength. You can actually read other people's code without much introduction to the concepts used in that codebase, because the number of possible meanings of any particular expression is much smaller than in other languages.
There is so much talk about Go being for dumb, second rate, corporate developers, because that's what Pike essentially said at one point (perhaps without thinking first).
But in fact, it's not the developers who are dumb. It's the process by which large corporations employ and dispose of developers. They are thrown into some project and expected to "hit the ground running". There's no time for explanation. So what they do is read code to acquaint themselves with the codebase and hopefully become productive before they move on to the next job. And that is the one task where Go really shines. Reading arbitrary pieces of code.
Of course powerful abstraction features eventually make reading code easier as well, but only after having learned the abstractions created for that particular problem and codebase and only if those abstractions are very carefully crafted.
Powerful language features help writers of code long before they help readers. And that, I believe, is essentially the dirty secret that Go exploits.
We all want to be brilliant writers of code when in fact we are often readers poking helplessly at half understood code to make something happen. We even forget our own abstractions once we haven't looked at them for a couple of months or even weeks.
The problem with Go is that it not only acknowledges this state of affairs, it also enshrines it.
Definitely. This really shines in the standard library, it consists of extremely readable code and is a good way to get up to speed on canonical Go.
We all want to be brilliant writers of code when in fact we are often readers poking helplessly at half understood code to make something happen. We even forget our own abstractions once we haven't looked at them for a couple of months or even weeks.
Definitely, but what are we comparing to? I would agree that e.g. C++ and Haskell have this property. Unless you understand the language and commonly-used abstractions, template-heavy C++ code is difficult to read. However, there are many languages that have more powerful type systems than Go, but where code is still easy to read (ML, Object Pascal, Oberon, Ada, etc.).
I wonder whether it is simply a theoretical tautology that the more abstraction features you have in a language, the more different possible meanings any particular syntactical expression can have, and the more effort it requires to figure out its true meaning, assuming you're not familiar with the codebase.
Or is that a false dichotomy? I am unfortunately not familiar with Ada or Oberon and Pascal is but a faint memory.
Iterative languages seem to match more closely how people speak/think in verbal language.
I want more enforced required clarity. I compared it at the time to writing English without any punctuation, you can do it but it makes comprehension much more difficult.
The Go language doesn't have to be crappy to provide batteries included API, version stability, and great tooling. No generics and no sum types? Those are no longer groundbreaking, they're the bare minimum.
Old languages like C at least had the excuse of being created a long time ago, when we possibly didn't know better (and computers were much slower). Go's creators however don't have that excuse. They screwed up, plain and simple.
I love Python but always wished for a simple, type-safe language; Go gives me that. It's not worse than Python IMHO.
I only advocate Go as a replacement for those that would use C for user space applications, or possibly some kind of low level stuff.
If Python and Ruby eco-systems had blessed compilers, instead of just CPython and MRI, I doubt people would be flocking to Go.
We already see this happening in the Ruby world, just let Crystal become a bit more mature.
Btw I don't consider myself a Go developer, at work I use C# and Python. I would love to learn Rust, used to be the other way around, but I've lost hope in Go and have taken a second look at Rust, I love the direction Rust is going overall, but I understand why Go got so popular so quickly, it came at the right time with the right amount of working parts.
Just because a language is founded on good programming theory doesn't make it unviable in the industry.
Ideally, the reverse would be true.
Galois, of course, is very much built on Haskell. And there are certainly other examples.
A company doesn't need to be built on a single language.
I mean the fact that there is even an exhaustive "Haskell in industry" page at all shows you how rare it is. There isn't a "C++ in industry" page! I did actually find a Go one here:
https://github.com/golang/go/wiki/GoUsers
But it's both hilariously long and also obviously not exhaustive.
Maybe you should spend some time learning how FB and Microsoft use Haskell, you might be surprised with what you find.
As a clue, try to find out how C# and VB got LINQ, maybe you will find the right MSR paper by a certain Erik Meijer.