Hacker News new | ask | show | jobs
by bluejekyll 3596 days ago
> “There is nothing new under the sun” rings true in all languages since the 80’s.

Really? Nothing? Sure a language like Rust has drawn from many other concepts in other languages, but it has done so while actually bringing high level features to a language that has zero overhead costs. But yes, it's not simple like Go.

Did Go need to make all errors unchecked? There are no guide rails telling you that you forgot to check an error result. This is a runtime thing you need to discover. Is this actually simpler?

Go made the decision to allow for Null, even after nearly every other modern language and other older ones are trying to kick it to the curb; Swift, Rust, Scala, Kotlin, no nulls (the JVM ones have a compatability problem, as does swift with ObjC, but still). Is it simpler to delay discovery of Null data to runtime?

Go decided to not have generics, to keep the language easier to learn and more approachable. It's hard to argue with this one. Like lambdas, it can be a complicated concept to learn, but once you unlock this in you code, you write less code and accomplish more. So yes, it's simpler, but at too high a cost IMO.

To me the innovative feature of Go is the small runtime built into the binary making deployment dead simple and easy. This is a million times better than JVM, Ruby, Python, Perl, etc. This is a huge improvement over Java, and something every language should have an option for. Ironically this is also the least innovative feature, because this is how static binaries in C and C++ have worked for years.

I think this article is very well written, but I don't think it's fair to the innovation going on in other languages.

(Disclaimer: I used Go, discovered the three primary flaws as I listed above, and then searched for a better language. It would be fair to call me a hater, usually I try to avoid this, but in this case that's fine with me)

9 comments

> Go decided to not have generics, to keep the language easier to learn and more approachable. It's hard to argue with this one.

Plain parametric polymorphism is super easy to understand. Standard ML could be learnt in a week by someone who doesn't know how to program.

Admittedly, the interaction between parametric polymorphism and subtyping is tricky and subtle. And it seems most programmers have gotten used to taking subtyping for granted. But what if subtyping isn't always a good idea? Say, because it forces you to reason about variance (which humans always seem to do wrong!).

(inb4: Yes, Go has subtyping. When a struct conforms to a given interface, that's subtyping.)

Honestly parametric polymorphism is a big slippery slope feature. There's always Just One More Thing -- higher rank/order/kinded types, where clauses, dependent types, specialization... or else you force people to use dynamic checks/allocations/casts that reduce your type-safety and bog the code down relative to the "optimal" design.

Don't get me wrong, I love me some parametric polymorphism, but it's by no means a simple thing as far as I've seen. Especially if you care about the effeciency of things (you can fudge a lot more wih lots of indirection/allocation, like Java does).

> Don't get me wrong, I love me some parametric polymorphism, but it's by no means a simple thing as far as I've seen.

I disagree. You should look at OCaml (and Standard ML, though the eqtypes in SML are a botch): generics are dead simple there. Much simpler than Go interfaces, in fact.

Sure, there's always "one more thing" you could add, but that's always true for anything in any language. Slippery slope is a fallacy for this reason.

> or else you force people to use dynamic checks/allocations/casts that reduce your type-safety and bog the code down relative to the "optimal" design

Which is what happens even more if you don't have generics!

> eqtypes in SML are a botch

Standard ML's `eqtypes` aren't a bad idea at all:

(0) They ensure that `op=` can only be used on things that actually have decidable equality.

(1) If SML were to be equipped with dependent types in the future, it would make sense to only allow `eqtypes` as type indices. First-order unification can be used on syntactic values of `eqtypes`, so the basic architecture of a Damas-Milner type checker can be retained, in spite of having dependent types.

OTOH, equality and comparisons in OCaml are completely broken.

> higher rank

A language designer can provide let polymorphism, refuse to add more, and call it a day.

> (higher) order/kinded types,

This is orthogonal to parametric polymorphism. Higher-kinded types are problematic for inference, and the way Haskell has implemented them has unfortunate consequences for modularity.

> where clauses,

This is just syntactic sugar. (FWIW, what I think Rust needs is better inference, rather than ways to make type signatures less verbose.)

> dependent types,

This is unrelated to parametric polymorphism.

> specialization

This is antithetical to parametric polymorphism.

> or else you force people to use dynamic checks/allocations/casts that reduce your type-safety and bog the code down relative to the "optimal" design.

Standard ML doesn't have dynamic checks or unsafe casts, and I don't find myself longing for them.

   This is orthogonal to 
   parametric polymorphism
Higher-rank types are not orthogonal to parametric polymorphism, instead they are a special case. You can see this when you realise that rank-k polymorphism is a subsystem of System F (the paradigmatic typing system for parametric polymorphism) for any k. The let-polymorphism of the ML-family is just rank-1. See Chapters 22 and 23 of Pierce's great "Types and Programming Languages".

   Higher-kinded types are 
   problematic for inference
That is true, but already type inference for rank-3 polymorphism is undeciable, and the same is true for System F polymorphism.

In practise, Haskell needs only few kind-annotations to make kind inference possible. This is helped by unannotated kind variables having kind * in Haskell (IIRC).

> Higher-rank types are not orthogonal to parametric polymorphism, instead it's a special case.

Errr, sorry, I only saw “higher-kinded”, not “higher-ranked”. But, of course, you are right.

> That is true, but already type inference for rank-3 polymorphism is undeciable, hence also System F polymorphism.

Let polymorphism covers 95% of what most programmers need. So if a language designer feels particularly risk-averse (a perfectly legitimate position), they can provide just let polymorphism and ML-style type inference, and then call it a day.

Of course, higher-ranked polymorphism is a nice thing to have, and you can require type annotations when you use more (as Haskell does).

> In practise, Haskell needs only few kind-annotations to make kind inference possible.

A more serious problem with higher-kinded types IMO is that they wouldn't interact very well with an ML-style module system, where you can define an abstract type whose internal implementation is a synonym. `newtype` is an ugly hack.

   provide just let polymorphism 
   and ML-style type inference
I mostly agree with this, and this should be the default starting point for any new programming language. If B. Eich had built Javascript on this basis, the world would have been a better place.

My main caveat would be that even a basic language needs a mechanism to glue related code together, objects, modules, structs with row-typing, existentials, not sure. But something.

> A more serious problem with higher-kinded types IMO is that they wouldn't interact very well with an ML-style module system

That's interesting. Why is that?

Structs implementing interfaces can be viewed as subtyping, but they don't have to be. An alternative is to take a typeclass-like view. Basically, a function "(A, A) -> A where A implements I" could be implemented as "(pointer to vtable of interface I, voidptr, voidptr) -> voidptr".

Not having "implementation inheritance" between structs helps a lot, though I'm not sure if Go's anonymous fields might pose a problem.

> An alternative is to take a typeclass-like view.

Type classes alone don't give you anything like Go's interfaces.

Type classes plus existentials give you something kinda like Go's interfaces, but requires explicit casts (in the form of unwrapping the contents of an existential constructor and putting them into another existential constructor).

Type classes plus rank-N types can express Go's interfaces, but at that point Haskell already has subtyping, induced by subclasses. (Or else how do you think “a Lens is a Traversal” is possible?)

I just spat out my coffee. Standard ML can be learned in a week by someone who doesn't know how to program?

Have you ever actually tried to teach someone who doesn't know how to program? It takes months, even when using a simple language like Python. Or even BASIC, which was designed specifically for beginners.

Standard ML is a good language (especially considering when it was developed, in the 1970s). Somehow a cult has grown up around it that prevents people from seeing that it isn't the solution to all problems, just another tool in the toolbox. Sad.

> Have you ever actually tried to teach someone who doesn't know how to program?

Yes.

> It takes months, even when using a simple language like Python. Or even BASIC, which was designed specifically for beginners.

I never said anyone can learn everything there is to programming in a week. I only said anyone can learn the Standard ML language in a week. You might encounter far more difficult things along the way, but they shouldn't be related to Standard ML itself.

The biggest irony of Go is that they have invariant parametric types for channels, arrays, etc. and indeed primops like channel sends, make, len, etc. are parametric functions. So for all the defensiveness about how parametric polymorphism is "difficult to understand", Go programmers seem to deal just fine with it on a day-to-day basis. What they lack is the ability to let programmers introduce their own parametric types and functions, presumably because we're too dumb to be anything but consumers of this functionality.
What they lack is the ability to let programmers introduce their own parametric types and functions, presumably because we're too dumb to be anything but consumers of this functionality.

Here's what my experience is with certain features in languages: They enable some programmers to do great things, while also enabling a few programmers blinded by hubris to do maddening things. Over the life of a large project, the unfortunate coincidence of different pieces of hubris driven code sometimes causes an outsized amount of frustration.

Analogy: Most people, most of the time, have the good sense to operate cars, drones, and high powered laser pointers without becoming a dangerous nuisance. However, there is a potential for a minority of users of such devices to cause far more than their share of public nuisance. Therefore, there are rules and restrictions about how such things are used by many people at scale.

So yes, as an individual you are probably just fine. But you aggregated with a whole bunch of other programmers is likely to be a different story.

https://en.wikipedia.org/wiki/Tragedy_of_the_commons

You can make this argument with nearly anything we naturally accept as a feature of a programming language. The ability to name things has a long history of abuse. The ability to define types, implement programming patterns, define new syntactic features via high order functions, use concurrency primitives. Hell, simply the idea of programming is rife with potential for abuse.

This is a narrative without specifics, and unfortunately always where the conversation seems to end with gophers. We accept some amount of features that can be abused because they offer utility that outweighs their potential for misuse. So how exactly are generics a worse offender than these other features or a worse tradeoff for their utility? Because from my perspective, being able to define parametric data types and functions is a huge win for safety and terseness of code without a lot of downside.

Hell, simply the idea of programming is rife with potential for abuse.

Exactly. Everything you add has a cost/benefit for a particular context. Evidently you disagree with how the Golang team has calculated cost/benefit with regards to generics.

Because from my perspective, being able to define parametric data types and functions is a huge win for safety and terseness of code without a lot of downside.

Terseness is a good thing? Some people say terseness is bad. Is safety the only issue or always the top priority? All production code exists in a specific context. It's best to tailor to your specific context. This may well mean that you may encounter a context where you do not want to use Go.

http://anomaly.org/wade/blog/2013/07/coding_style_terse_vs_v...

> Terseness is a good thing? Some people say terseness is bad.

Clarity is good. Clarity comes from both including every relevant detail (which pulls away from terseness) and excluding irrelevant details (which pushes towards terseness). Clarity also comes from saying everything that has to be said exactly once and no more than that (which pushes towards terseness).

Unfortunately, when you program in Go, you often have to pay attention to irrelevant details, and you have to say what you want more than once.

> Is safety the only issue or always the top priority?

The benefits of typeful programming go beyond type safety. They also include: “economy of thought”, “fearless refactoring”, “less time wasted on fixing stupid mistakes”, etc.

If we consider Go in the context of a systems (their definition) engineering language within Google's enterprise, limiting choice is not about how dumb the programmers are, but about ensuring conformity.

Rewriting code is expensive. As my office knows well. We maintain lots of old embedded systems and have to periodically rewrite or rehost it because the old hardware platforms aren't available or aren't performant enough for new features. These become multi-year, multi-million dollar projects, for relatively little gain.

By ensuring that developers and architects conform to certain conventions, it means (in theory) that this code maintenance is much cheaper, and that rewrites can be avoided or minimized. This is a good thing and lets organizations be more flexible and productive, as their time and money is no longer wasted on the old things, but can be spent on the new things.

> limiting choice is not about how dumb the programmers are, but about ensuring conformity.

What you say doesn't make sense given how Go reflection is implemented. If it was really about limiting choice Go would have no reflection . Go reflection is basically a way to opt-out of its (poor) type system. You should never have to do that in a statically typed language yet Go reflection is used a lot in the standard library itself.

Furthermore let's be honest. What do you think is more complicated ? generics or concurrency ? generics aren't complicated, at all.

> We maintain lots of old embedded systems and have to periodically rewrite or rehost it because the old hardware platforms aren't available or aren't performant enough for new features.

But Go isn't for embedded system programming. You can't run Go on bare metal without an OS.

> By ensuring that developers and architects conform to certain conventions

Enforcing conventions is of course a good thing! The problem is how Go enforces conventions:

(0) When Go enforces a convention mechanically, it's a triviality that can be adequately handled by external tools (e.g., naming, formatting, unused variables, etc.).

(1) When a convention is actually useful (e.g., the correct way of using an interface), Go's type system is too dumb to understand it, let alone enforce it.

> aren't performant enough for new features

Second-class parametric polymorphism (“generics”) is purely a compile-time feature. It can be completely eliminated (that is, turned into the non-generic code you would've written otherwise) using a program transformation called “monomorphization”, before any target machine code is generated. So there's no runtime price to be paid.

To be precise, you need to outlaw polymorphic recursion to be able to do full monomorphisation. I'm not sure if that's what you meant by "second-class" in this context
First-class polymorphism is what System F gives you: functions from types to values.

Second-class polymorphism is what Damas-Milner gives you: let-bound identifiers may admit more than one type, in which case every type they admit is subsumed by a type schema.

Second-class polymorphism rules out polymorphic recursion if you consider every recursive definition as syntactic sugar for applying a fixed point combinator to some expression of type `a -> a`, for whatever monotype `a`.

FWIW I'm not trying to strawman the argument behind not having features like this. Rob Pike said this in a talk about Go:

"The key point here is our programmers are Googlers [...] They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt."

I'll concede there's the possibility for some weird tongue-in-cheekness here, but it definitely seems to be the canonical view among gophers that Go's paucity of features is about accessibility for programmers that don't understand them or find them cumbersome to work with.

I think this idea that "paucity = good" is so easily abusable that whenever this comes up from gophers I wish they would concede that this is an unhelpful simplification of what they must actually believe. Assembly language has possibly the highest paucity of concepts given it offers no ability to introduce language-level abstractions (other than say conventions about calling, etc.), but Go is nothing like this.

The argument can't be that paucity is good as a general condition, its that there are forms of abstraction and programming language features that Gophers find unhelpful or difficult to understand. The problem I have with this when applied to parametric polymorphism, is that Gophers already work with these concepts daily, so it can't be that use of them is complicated.

I also have a hard time believing that the ability to define parametric types and functions costs you anything. It's almost always self-evident when to use parametric types or functions, things that are "wrappers" or "collections" probably account for 80% of their use. I also don't think I've ever experienced ambiguity of choice with the feature. For instance I don't think I've ever been in the situation where I had to trade off implementing a generic definition vs. N specialized definitions. The frustration of using Go is actually that I now have to consider the later as a possibility or trade off type safety by using unsafe casting.

If there's a place where parametricity truly introduces complexity, I'd love to hear about it from a Gopher instead of a blanket statement about how "programmers don't understand it", "it decreases readability", or "Go is simpler without it".

seems to be the canonical view among gophers that Go's paucity of features is about accessibility for programmers that don't understand them or find them cumbersome to work with

Please keep in mind that there are differences at scale. What is "easy to work with" for 1 programmer over a month might not be so for 20 programmers over years.

The argument can't be that paucity is good as a general condition, its that there are forms of abstraction and programming language features that Gophers find unhelpful or difficult to understand

The argument is that simpler is better at scale. Airplanes can move freely in 3 dimensions, but airliners are constrained to fly in particular ways around busy airports and cross country.

I also have a hard time believing that the ability to define parametric types and functions costs you anything. It's almost always self-evident when to use parametric types or functions, things that are "wrappers" or "collections" probably account for 80% of their use.

I could see an argument for parametric collections and parametric sorting in Go. Not, however, for wrappers.

The frustration of using Go is actually that I now have to consider the later as a possibility or trade off type safety by using unsafe casting.

In your experience, what kind of "cost" has there been in unsafe casting to use collections? Even in environments like Smalltalk, where all use of collections amounts to "unsafe casting," I've rarely seen situations where a mistake of this type wasn't found trivially. Does your frustration come from having to abandon the "assured safety" the type system would give you, or does it come from an experience of the costs?

> * Does your frustration come from having to abandon the "assured safety" the type system would give you, or does it come from an experience of the costs?*

For me, it's entirely about expressiveness. This:

    reverse: 'a list -> 'a list
where the type encodes that `reverse` is a function from a list of some type of elements to another list of the same type of elements, is more informative than this:

    reverse : list -> list
where it's obvious that this list has elements of some type, yet it's not clear what the element type is, and it's not clear that the resulting list has elements of the same type as the input list.

Beyond being able to express and communicate intent, there's the added benefit that the type system can statically check that the input elements are the same type as the resulting elements. There's also no worry about information loss associated with subsumption (the rule of subtyping that allows a value of a subclass to "become" a value of one of its superclasses, losing specificity in the process which may only be regained with a type cast (this is one reason I tend to favor row polymorphism as well -- no subsumption means no information loss and no need to cast)) because no subtyping is involved in this case of parametric polymorphism.

> The argument is that simpler is better at scale.

Parametric polymorphism is simple and well understood. And not exactly new either: it has been understood for some 40 years already.

> I could see an argument for parametric collections and parametric sorting in Go.

C++'s <algorithm> header is proof that there are lots of algorithms that benefit from being expressed generically, not just sorting.

> In your experience, what kind of "cost" has there been in unsafe casting to use collections?

Without type safety, there's a disincentive for decomposing things into smaller parts, because the cost of manually verifying that the parts are compatible is greater than the benefits of decoupling them. Would a Go programmer even dream of bootstrapping fancy data structures from simpler ones?

> Even in environments like Smalltalk, where all use of collections amounts to "unsafe casting," I've rarely seen situations where a mistake of this type wasn't found trivially.

At scale, the law of large numbers says that even improbable events will occur every now and then. Unfortunately, a program with even one bug is still incorrect.

> Please keep in mind that there are differences at scale. What is "easy to work with" for 1 programmer over a month might not be so for 20 programmers over years.

> The argument is that simpler is better at scale. Airplanes can move freely in 3 dimensions, but airliners are constrained to fly in particular ways around busy airports and cross country.

For one, I just debate the premise the simplicity has anything to do with cardinality of features/concepts. But let's take that argument at face value: then why _not_ assembly if this is the case? Why not a language with the absolute minimum number of concepts? I think if you interrogate this premise you'll find it doesn't hold a lot of water and that Go doesn't really aspire to this goal anyways. I think we have some amount of working memory for being able to intuit programming with a certain number of concepts. There's a valid argument that some languages suffer by breaking that barrier (though I personally think Go underestimates where that barrier is), but it seems incorrect that language designers should be optimizing for a minimal number of features.

I think complexity at scale has more to do with features that interact poorly (or cause poor interactions more frequently with a larger number of people). Specifically its about composition. For instance, there's a valid argument to be made that asynchronous exceptions (i.e. the ability to interrupt another thread with an exception) and locks poorly compose. Mutable state is a common example of a feature that's a detriment to composition. But parametric polymorphism, if anything, gives us a much greater ability to compose. It allows us to define functions that work on data arbitrarily parameterized by other types, which makes them conducive to composition. And likewise, we don't suffer ability to reason about composition at scale with parametric types. A parametric function does not gain complexity as more team members are added, more code is written, more deadcode accumulates, etc. Parametricity changes nothing at scale.

> In your experience, what kind of "cost" has there been in unsafe casting to use collections? Even in environments like Smalltalk, where all use of collections amounts to "unsafe casting," I've rarely seen situations where a mistake of this type wasn't found trivially.

That's an argument for Go to not have types. But Go does have types, and type safety is often espoused as a benefit of Go. If you're going to have types, it makes zero sense to me why you should not have parametric polymorphism, since this is the only way to have things like typed collections without opening yourself up to the possibility of casting errors. Frankly I find it bizarre that people claim that they have found type errors to be trivially fixable, because the scope of where a type error can be introduced is enormous in an untyped language... its literally every location that potentially calls into the code where the error occurs.

> Does your frustration come from having to abandon the "assured safety" the type system would give you, or does it come from an experience of the costs?

Yes, type safety is an enormous advantage to writing correct code in my opinion. It's one of the best mechanisms a programming language can give you for enforcing invariants about data. The curry-howard correspondence is a huge advantage to writing correct code. Every place a type checker isn't being used to delimit acceptable data is a potential source of a huge number of bugs. It's also a frustration because casting introduces conversation and type checking boilerplate that a type checker could ultimately take care of for you.

Ouch. That quote is incredibly unkind to his colleagues. I remember back when Google practically required at least a masters degree for their new hires. Throwing out Georgia Tech grads with 4.0 GPAs and only a bachelors.

Guess they don't think so highly of their hires anymore.

We are yet to be convinced a degree or GPA have any correlation with an actual abilities related to programming with regard to quality, performance, abstractions, languages, tools or anything.

By the way, http://www.deathandtaxesmag.com/200732/google-admits-its-fam...

> There are no guide rails telling you that you forgot to check an error result. This is a runtime thing you need to discover. Is this actually simpler?

This may be considered cheating since it isn't baked into the language but there are tools to do this at build time, here's one: https://github.com/kisielk/errcheck

There are lots of tools in Go that make up for issues people have with the language. Another example is the IDE macros people use for the standard if err != nil {} block.
> Another example is the IDE macros people use for the standard if err != nil {} block

Which doesn't make code easy to read when there are 10th of these blocks in a single function.

It may be 4 lines of code where 1 or 2 would do, but I don't find it hard to read.
Thank you, and may I say what a well written comment.

Poor old Tony Hoare (algol was mentioned earlier (rightly) as an exemplar of innovation) but Null in a safe memory managed context is a different beast to a true Null reference.

Null appears then as something between a known state and an not quite an exception, it carries different semantics from either, and whilst this could be seen as more complexity, I think the "I just don't know" case in practicality is useful, if harder to reason about.

Your point about the runtime is very true. Partly because of this - error checking is overrated! Yes I said it! We have go code that has been running in a reasonably high scale production environment for over two years and there are `if:...;err != nil` blocks that have never been touched in millions of calls per day, for 2 years. We have redundant services and trap panics in the rpc handlers, the nil becomes very clear and the rest of the system makes good progress. We save lines, save tests, and release a single binary fast. One example of where Go helps us deliver value faster, by being able to choose to ignore exceptions. Many people find this very uncomfortable. I say they are mistaking where the true project risks lie.

> being able to choose to ignore exceptions. Many people find this very uncomfortable. I say they are mistaking where the true project risks lie.

It's funny, every language I know gives you an option to basically ignore the error and just pray. I get what your saying, but if this is the type of code you want to produce, you can still do that in other languages that have strong types around Null and Errors.

In Rust for example:

  my_possible_error.unwrap()
In places where you are explicitly making that choice. And to me that's the big difference. Is it explicit or implicit/unknown?
Fair point, just differing levels of hoops to jump through, or general expectations of best practice. In that sense I would say Java and C# do not allow you to 'ignore' exceptions.

I suspect your "pray" == monitor closely and fix fast (often never).

I agree it can be problematic that there is nothing in the language that indicates that the author is explicitly in 'pray' mode. It could well be that even a brief defence of a missing error check during a PR code review is not worth avoiding (un-triggered) err handling.

What better language did you settle on?
Probably Rust.
Yep, but I think there are others that will fit people's needs as well. Rust suits my needs/wants perfectly.
Rust really impresses me because the community and the ecosystem, but I've not looked at it too much.
>innovative feature of Go is the small runtime built into the binary making deployment dead simple and easy.

Is rust the only good alternative here to golang if you one doesn't want to write c/c++ ?

OCaml and Haskell build self-contained binaries, and have done for decades. OCaml has very fast compile times, and an ordinary C-like linking system. You can even directly link C *.o files. Go certainly isn't "innovative" here.
And if you don't need types, there's LuaJIT with all the dynamic features, best inline C FFI ever done, runtime performance in par with native compiled languages, and tools for producing self-contained binaries if needed - https://luapower.com/bundle
Rust is not an alternative to Go. Go is significantly easier to learn as it's very simple. Crystal and Nim come close but they do not have the backing of a large tech company and their ecosystems are not as mature.
There are much more robust and advanced programming languages with native code compilers, imperative programming support and decent code performance - e.g. OCaml.
Nim is another good alternative. Small, dependency-less binaries are one of its strong points.
There are many compiled languages with embedded runtimes. Haskell is among the best of them.
> haskell is among the best of them

There is little need for this kind of absolutism. Citing a haskell as one the best language in a contest where op ask about rust and c++ is dangerous.

To op : if your domain calls for modeling relatively 'type stable computation', and need strong correctness garanty, haskell is a great match.

How is it dangerous? Your qualifying remarks with regards to Haskell's domain make no sense. If you need a fast, compiled language with managed memory, high ease of development and a strong ecosystem then you can't go wrong with Haskell.

'Type stable computation' and a strong correctness guarantee are some added benefits of Haskell, though any strongly typed language (like for example Rust) will have these qualities.

A nice benefit of Haskell that most other languages don't have is that it is explicit about side effects which gives you some extra confidence in the behaviors of your code. Related to this is its unusually powerful type system, which allows you to make some abstractions for generic code that are not possible in most other languages.

Haskell's runtime has one overriding attribute: laziness. If laziness is not desirable in your domain, Haskell is not a useful option
FWIW, strictness may be introduced into haskell programs. Weak-head normal-form evaluation is builtin with the "seq" function and the "deepseq" library is commonly used for fully normal form evaluation of expressions.

GHC 8 also introduces the Strict and StrictData pragmas[1] which allow you to make a module (or its types) fully strictly evaluated.

[1]: https://ghc.haskell.org/trac/ghc/wiki/StrictPragma

Can you give me an example of a domain where laziness is not desirable? I only do Haskell for side projects, so perhaps I lack exposure to some domains.
Almost all static compiled languages have AOT compilation to native code, even Java and .NET ones, although many tend to ignore it.

So all the ML languages, Java, .NET, Pascal dialects, Modula-2, Modula-2, Oberon and its descendants, Ada, Crystal, Nim, D, Rust, Swift, Objective-C, ...

Definitely Swift,but is not as mature yet as the other options mentioned.
What is the better language that you found?
(Copied from below): Rust, but I think there are others that will fit people's needs as well. Rust suits my needs/wants perfectly.
Hey, Are `integration tests` potato here!?
"Like lambdas, it can be a complicated concept to learn, but once you unlock this in you code, you write less code and accomplish more"

With the caveat that anyone who may be maintaining/using/enhancing your code will also need to be able to "unlock" this. Keeping the language simpler has benefits beyond the initial code development.

> lambdas, it can be a complicated concept to learn

I'm curious, when was that being said? (Perl 5 was released in 1994, JavaScript 1.2 in 1997)

Or, you know, we could actually learn something in accordance with the nice pay most of us get.
This is one of my biggest frustrations when these sorts of discussions come up. It seems a great many programmers -- amateurs, students, and even professionals -- resist learning anything new.

As programmers, we deal primarily in abstractions. Our programming languages offer formal tools for creating and manipulating abstractions. In my view, any language that offers more tools for abstractions is better than another language that offers fewer such tools. As a professional whose primary job is to deal with abstractions, any new kind of abstraction is of interest. All programmers should be not only willing to be constantly learning new techniques and new abstractions, but we should be eager to learn and apply these things. Bigger toolbox => better quality of life w.r.t. work.

Even at my day job, I've heard things like "that's too computer sciency for mere mortals". I'm sorry, are we not computer scientists? Are we in the habit of employing people who are not professional programmers to write our software? And to think I'm the only developer in the office without a master's degree, as if they all decided that once they graduated they were finished learning...

Heaven forbid you should have to learn something! To educate yourself! To grow in terms of knowledge and skill! Do we have "development goals" every year for no reason at all?

As if spending an hour or two learning something would kill you!

AAARRRRRRRRGGGGGGGGGGGGGGHHHHHHHHHHHHHHHHHHHHHHHHH!!!

And to think we then preach "disruption" to everyone else. It's just jawdropping.

Allow me to disrupt your null and Interface {}.

> This is a huge improvement over Java, and something every language should have an option for.

I am not sure what improvement you are talking about. Deploying Go apps requires recompiling for the target platform. On JVM, you only need to install the JVM. There are also tools to wrap JVM apps in executable files that will automatically download and install a suitable JVM.

I'm specifically talking about the classpath, jars, separate jvm install. These are a pain to manage across environments.

I'm a longtime Java engineer, it's a great language, but I do think the compile once thing isn't as big an advantage anymore.

With the advent of the LLVM, it's easy to target specific machines. rustup, even makes it possible to build binaries for every target environment you have.

And let's be honest, how many people target more than Linux/x86_64 on the server side? Even if you target FreeBSD or Windows, my bet is that your still generally only targeting one platform.

Btw, Rust has a great std lib that is very portable across all major platforms. https://doc.rust-lang.org/book/getting-started.html

Java is battle-hardened and has seen almost every situation in the business programming. Go has miles to go before it can even be eligible to be compared to Java in terms of productivity and maintainability.
Fair enough about having to install a JVM separately. In my experience, it isn't a huge deal, but it's certainly a nonzero-sized deal.

But the classpath and jars? Write your application, 'gradle distZip', copy zip to target and unzip, invoke the launch script Gradle generated, done.

And if you can't be bothered to unzip, there's: https://github.com/pivotal/executable-dist-plugin

> I'm specifically talking about the classpath, jars, separate jvm install. These are a pain to manage across environments.

I agree with the classpath issue and hopefully it will be fix in Java 9. Separate jvm install? Why? JVM is backwards compatibility.

> And let's be honest, how many people target more than Linux/x86_64 on the server side.

Those that develop non server apps since Java is a general-purpose language.

Yes. I'm serverside generally.

The JVM is, but sometimes there are things that require specific bug fixes in the GC for example where it's just a big issue combining the jars with the JVM etc.

My only put is that a single thing to deploy is easier than multiple.

> I'm a longtime Java engineer, it's a great language, but I do think the compile once thing isn't as big an advantage anymore.

So you surely should be aware of the existing options to compile Java to native code, just like Go.