By putting "we're okay with being okay" as your Big Thing you're clearly pitching for that vast bulk of mid-quality developers that make up the huge middle chunk of corporate devs, the space where Java reigns supreme.
By putting "we're okay with being okay" as your Big Thing you're clearly pitching for that vast bulk of mid-quality developers that make up the huge middle chunk of corporate devs, the space where Java reigns supreme.
An excellent example is the handling of this bug report(§):
https://github.com/golang/go/issues/12914
which resulted in this language-change proposal:
https://github.com/golang/proposal/blob/master/design/12914-...
And then these commits:
https://go-review.googlesource.com/#/c/36255/
I feel like any other language I've used would've just added a "monotonicTime" function and called it a day; the Go solution is impressive, elegant, and shows an exceedingly high level of care for the end-outcome results for the users of the language.
To me, that's what makes Go special.
(§After a rocky start; it took the core devs a bit to realize it was a real issue because within Google's special environment it isn't.)
It's kinda the equivalent of making a new language (nowadays) and not defining what encoding your native strings use. Of course it's going to cause problems. Except the timer issue has been around much longer than the standardization on utf-8.
But personally I don't agree with the decision - it adds surprises when you try to convert times to other types and then compare things. Or convert to something else and then back to a time instance. I've watched things like this become the source of bugs many, many times - this will only make that worse, since now a "time" is two values but you can really only access one of them (the one that's equivalent to wall-clock, which is causing the problems). And they're particularly painful bugs, because the cause lies in implicit conversions and behavior. That seems to run counter to the "minimal surprises" that Go seems to prioritize.
But, I could easily see looking back in 2 years and realizing you were right.
That code-corpus-analysis is pretty neat, I hadn't looked through in detail before. Thanks for pointing it out!
---
Sorta as an aside, it seems the proposal can lead to:
t1 = time.Now()
...
t2 = time.Now()
diff = t2.Sub(t1)
t1.Add(diff) != t2
Times are hard :| t1.Add(diff).Equal(t2)
would still hold, because Now gets monontonic time, Add and Sub maintain it, and Equal checks it. // time = [wall, mono], just ints for simplicity
t1 = time.Now() = [10, 10]
t2 = time.Now() = [19, 20] // wall clock lags slightly on second measure
diff = t2.Sub(t1) = (20 - 10) == 10 // Sub only operates on mono-time
t1.Add(diff) == [20, 20] // Add adds `diff` to both wall and mono
[20, 20] ==?== [19, 20]
I'd expect those to be different, since they represent different wall-clock times.So t2.Equal(t1) is true.
You are right that "t1 == t2" (comparing the raw bytes of the time structures) is false, but that was already broken anyway (because time-zones break raw byte equality).
(Whether you think it's OK to have a language that doesn't overload == and makes you know that for complex structures you've got to call a method like Equal() probably correlates pretty highly with your opinion of Go.)
There will of course be other weird things, like:
print(t1) // "20 [20]"
print(t2) // "19 [20]"
But you could argue that's basically the least-surprising possible result in the face of the surprising fact time having gone backwards one second. :)By the way, worth mentioning that while this is all implemented in HEAD in Go, it's not a done deal yet -- it'll have a 3-month window for people to try it out in the real world, and if it's found to be problematic, it'll get backed out before becoming part of the official release.
Gotcha, I missed that somewhere. That's essentially fine - then the only real surprises are when you deserialize a Time (so it doesn't have a mono time), which should in principle have no interactions with this proposal.
And yeah, Equal vs ==, the meaning was clear enough that I didn't bother to be specific :) Thanks for the infos!
Elegant? Hell, no. It's an ugly hack.
Seems to be some cognitive dissonance (in the modern erroneous sense of the word) going on. This was a hack necessary because the design of the library wasn't fully thought out. Imagining the world would one day become Google is not thinking things out.
Easy to say 'not fully thought out'. It took Java from version 1.2 to 1.8 to fix its date/time library.
It took Java from version 1.2 to 1.8 to fix
its date/time library.
That is incorrect[0]. When spreading FUD[1], expect assertions such as these to be fact checked by others.0 - http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22joda-time...
1 - https://en.wikipedia.org/wiki/Fear,_uncertainty_and_doubt
And that's just an example that was intended to illustrative.
You'll just have to take my word for it that my experience of the Go ecosystem, tools, and standard library has been more solid than the various other things I've used over the years (python, C# + unity, c++, fucking maven, boost...).
If I'm missing out on something even better, let me know. ;-)
> you're clearly pitching for that vast bulk of mid-quality developers that make up the huge middle chunk of corporate devs
That was actually something Google aimed for with Go, and I don't see what is shameful about it. It's a simple language on purpose. It is meant to be easily learned by any developer, and similarly Go code is meant to be easily read.
Also, what sets Go apart for me, in addition to this simplicity, is the tooling and documentation. I think people mistakenly yearn for languages that are exciting to use or offer interesting abstractions. This stuff is fun for the individual developer but costs an organization in the long run.
Go is bland on purpose. Notice how between versions most of the headline updates are internal improvements (GC, tooling, general performance)
Go, however, is like a Toyota Corolla or a Honda Civic. It's dependable, but relatively plain. It's not necessarily a joy to drive but there's not much of a learning curve. It lets you go fast, but not so fast you'd hurt yourself. It's designed with safety in mind, so much so that it can feel a little over-bearing at times.
It's not a language that draws you in with one clear feature or philosophy. I've used it very successfully and I still find myself rolling my eyes at "go-isms." Lines of "ok, err = ...", rewriting extremely similar data structures, careful channel management, etc. In a list of dependencies, the go dependency will need the least maintenance time and tuning, but what you do need will often be drudgery.
Go is a useful language, but it's a language that has purposefully stripped out all the magic. People like magic, they miss it.
I agree with everything you wrote except this. After 3 years in a job where I had to use Ruby on Rails, I am utterly tired of "magic" and I regret the many hours of my life I have wasted debugging issues that were created/concealed by it. Lack of magic is a feature in my opinion. But as you said, that's all this is - opinions.
And actually, this is another place Go shines. Refactoring Go code is easy because the compiler helps you along. There's even standard tooling that automates some of it. Amazing.
They can, but that doesn't necessarily mean they do.
For example, the usage of Elixir macros, which are the primary metaprogramming tool in Elixir, is discouraged in the docs:
http://elixir-lang.org/getting-started/meta/macros.html#fore...
E.g. it won't let you overload your engine by using a very special feature that lets you get away with it, but it'll let you fly fast and enjoy afterwards how trouble free the experience was.
OK, I think I'll stop with the mataphors now :)
https://warisboring.com/stop-disrespecting-the-turboprop-c00...
Agreed, and I'd further argue:
1) most people driving Ferraris aren't skilled enough to drive them properly
2) most people driving Ferraris have no need to corner at 120 MPH
3) most people driving Ferraris are just trying to show off
4) most people driving Ferraris are more likely to hurt themselves and others than to save the day through the car's performance and handling
(To be clear: I think we're in agreement, but the metaphor is worth explaining in depth)
Level 1: let interns use Go(-karts), like their parents did with Basic.
Level 2: most languages. Proper training required and you still have to be careful.
Level 3: C, C++, critical code, kernels, crypto code. Training a commercial airline pilot takes years; and while this stuff is not hip enough for some, there's plenty of demand.
Now of course everybody makes mistakes, but if you deliberately choose C when there are better options available then you should pay out of pocket for your buffer overflows and 0-day exploits.
Go is the most fun language I've been using. On the contrary, I don't understand how some people can enjoy Java.
The language itself makes it easier to figure out what large code written by somebody else was mean to do - not that everything would be readable nor anything like that, but easier then say ruby or javascript.
The only problem with this argument is that everywhere - even in this thread - there are people saying that Go is a joy to program in. It's not lacking magic, it's magical in its own right.
A better car analogy would be that Go is a Lotus Elise. It's nowhere near as powerful as your friend C++'s Mustang or Rusty's McLaren. But to C++ and Rusty's dismay Go's little Elise still gets around a racetrack crazy fast. It may have a smaller engine but it's just taken a really different approach to going fast which works really well. And Go has a much more pleasant time getting to his destination than C++ and Rusty do.
>>The only problem with this argument is that everywhere - even in this thread - there are people saying that Go is a joy to program in.
Not sure why it's a "problem". I love driving my 2004 Honda Civic, and prefer it to newer, fancier cars. It's fuel-efficient, drives very well, and has not given me an ounce of trouble. So yeah, the comparison to Go seems very apt.
I've seen enough languages to make comparisons.
Some languages get in my way and slow me down as a developer. For example I'll always pick Ruby over Java if I have to deliver something quickly. However some languages get in the way of the CPU and slow it down and if that's important I'll pick Java over Ruby.
So both Ruby and Java can be good or bad languages depending on the use case. Some languages make both the developer and the CPU lose time so I won't say they are good.
I have no first hand knowledge of Go but it seems it's fast so it should be good at least where the CPU matters.
My impression is of a community willing to go without some features, in order to preserve those it values. Namely simplicity, explicitness, terseness, and consistency.
Mediocrity? The preference for writing loops over simple maps or folds is a bit mediocre. IIRC in Tim Sweeny's "Next Mainstream Programming Language"[1], he notes that around 90% of all the loops in Unreal are folds or maps. Maps and folds are just simpler than loops, without even appealing to terseness and elegance.
Hey I know I'm in no place to judge -- the creators of go and Google overall have accomplished more than I'll ever do. I just really cannot grasp the mindset and penchant for unexpressive languages.
The argument for maps and folds is the same as the argument for structured programming in general: using common/reusable idioms brings clarity and familiarity.
Although Python's approach is not terse -- `fstyle = [f(x) for x in arr]` -- it is eminently recognizable.
Your argument is not really about anything I specifically said; and without something to counterbalance, it argues against structured programming, too.
My argument is that "recognizability" is in the eye of the beholder. One programmer's "map is a powerful abstraction over transforms, for example loops" is another's "this is some cutesy code/math creole by a CS graduate who desperately wants to find nails to apply his functional programming and applied math hammer on".
That's a fairly harsh way of saying that at some point the abstraction isn't clarifying, whether it's recognizable or not.
Disclaimer: I'm a big fan of functional idioms (they're one of my favorite parts of Rust, for example). I'm not so much a big fan of absolutism or over-generalization.
Compare that to:
out = []
[1,2,3].each do |x|
out.append(x.to_s)
end
IMO that's not fad material - that's progress. Maps and folds can totally be abused to produce inscrutable nonsense, but for small, common operations they're often much more obviously-correct.[1]: `&:method_name` is extremely-common shorthand for "call this method"
A tale of two cities is an easier book for an English speaker than Les Aventures de Tintin.
Map applies a function to each item in a collection.
As pseudo-code:
Collection.map(function)
And in the parents example we have:
[1,2,3].map(&:to_s)
The only Ruby specific part is &:.
Summing is a nice example. A naive for-loop will just iterate through the list, keep a variable around, add the next item in the list to it. In functional programming (or with a reduce), you tell it to sum in a more concise way - but more importantly, you don't tell the compiler how exactly it should do it. It's trivial with functional programming to make the task (for example) multithreaded or to use advanced underlying cpu tricks, without you as a developer needing to know how exactly it does what you ask it to.
I'm unclear where algorithmic complexity is coming into this. Can you clarify what you're trying to imply?
Can you given an example where map might increase algorithmic complexity like that where the imperative version would not?
What makes map different is that it separates the looping mechanics (incrementing, initializing and appending to the collection) from the actual computation we want to perform on each element. It's just separation of concerns.
You're right: map separates looping mechanics from computation. In Go, that's a bit too much obscurity.
If such a language existed and had generics and Swift or Rust style error handling as well as some backing by a large-ish corporation / organization, I think that language would be preferred to Go.
More expresiveness isn't always better. Examples that hit the sweet spot are C# / Swift (C# really needs algebraic data types). Beyond a certain point, you will start to lose users. Haskell is very expressive, but few will ever have the time or patience to learn enough to build their perfect monad transformer stack and take advantage of the mtl typeclasses to easily use it. Even though it does have M:N green threads, channels, STM and everything.
In fact, Haskell could probably compensate for this and beat Go by having excellent library documentation and well written, focused tutorials for the working engineer.
More importantly, how many character needs to be used for simple idiom like that has zero influence on how maintainable your system is going to be few months later on, how fast it is and so on. The difference between loop and map wont make you do less or more bugs. It may make you read the code few seconds longer first time you encounter form you are less used to - but then you will adjust and read it just fine.
The Python approach -- where maps and loops share a lot of syntax -- is maybe the middle way. On the one hand, it's not an approach that gives rise to `.map()`, `.collect()`, `.flat_map()` and so forth; on the other, it's marked out as something returning a result.
Writing code in terms of small, composable, reusable functions with a clear single intent (through good naming), that you can then mix, match, and reuse to compose into larger functions is what makes good functional code so much easier to write, test, and reason about than imperative code.
My favorite intro to functional programming concepts for those used to imperative coding is Sott Sauyet's Functional Programming presentation: http://scott.sauyet.com/Javascript/Talk/FunctionalProgrammin...
The presentation makes heavy use of JavaScript and the RamdaJS library in its examples, but the concepts are universally applicable to any language with the necessary functional programming primitives. It also does a great job of comparing imperative and OO implementations of a solution to a problem vs the functional implementation. I highly recommend taking a look if you're even slightly interested in why so many people are starting join the functional programming bandwagon.
So what I'm trying to say is, it's not just a matter of readability. Although if anyone still wants to argue that imperative looping is anywhere close in readability compared to functional composition for non-trivial cases (think multi-level nested loops vs composing multiple functions), then we should just agree to disagree since I don't foresee that becoming a productive discussion.
Absolute, that holds for non functional code as well.
It just does not matter whether the smallest function inside that system of functions has a loop or a map inside it. Loops are as easy to tuck into reusable functions as maps or anything else. Notably examples in the presentation you link have functional version shorter, but harder to read - they have more features however. Even the first one pipe(..., reduce(add, 0)) just does not read fluently.
But again, this low level has very little influence on maintainability of the larger program. In anything that is not computational library, how you compose them matters more. It is as if you assumed code with loops can not be split into smaller composable units.
I worked with codebase written in largely functional style. It was hard to read at first, but then I got used to it. However, it never became all that much easier to read them loops nor easier to work with. It gets bad when people get clever with composing functions.
This is certainly true! You can definitely wrap all your loops in functions that take in a collection as argument, and return another collection or value, and as long as you don't produce any externally observable side effects with your loops, these functions are as good as any other as building blocks of a functional program.
Consider the age old adage: "If a tree falls in the forest and nobody hears it, did it actually fall?", similarly, "If a function mutates some internal state only visible within its own scope (a counter or accumulator in an imperative loop, for instance), did it actually mutate anything?" The practical functional programmer would answer with a resounding "no". In fact, RamdaJS itself is implemented mostly imperatively internally for performance/compatibility reasons, but exposes a functional API for the consumer. Similarly, Clojure is implemented the same way for many of its core functions, and exposes an easy way for users to perform mutations for similar purposes within the functional, immutable-by-default language using transients: https://clojure.org/reference/transients
If you use imperative loops by wrapping them in this way, however, note that you're essentially implementing the same function signature of a function that calls map/filter/reduce on a collection, but with imperative primitives, and that's definitely a valid approach. If you build your programs by writing and composing these kinds of functions, you are in fact doing functional programming, and can reap all the composability, maintainability, and testability benefits that come with it.
I don't think there's any room to debate that most developers don't use imperative loops in this way, however. And my point was that the way they're usually used was not trivially composable. Of course, you could always refactor them into small functions that are trivially composable, but that they need to be to become composable is why I prefer using trivially composable primitives like map/reduce/filter to implement my programs as a default, and optimize specific functions with imperative implementations on an as-needed basis after profiling and identifying all the critical paths (premature optimization, and whatnot).
RE: `var sumOfSquares = pipe(map(square), reduce(add, 0));` not reading fluently.
This is where we'll have to agree to disagree. To me that reads quite literally as "given a collection, return the square of each item, and add the result of each to the next, starting from 0, to return the final value". The functional approach could definitely look more intimidating to readers without any general knowledge of functional primitives and what they do, but that's an issue of familiarity, rather than one of inherent readability.
Yes, people can get crazy with nesting multiple inline composed functions inside of other inline composed functions on one insanely long line, and that can quickly get out of hand in terms of maintainability and readability, just as people can get crazy with nesting loops. But that's just a case of bad functional code that needs to be refactored using a composition of smaller, well-named functions broken down into multiple lines. Nobody is claiming functional programming is a panacea for bad code.
If you want faster or lower level, you gotta move to C++, which is uber-hardcore in terms of non-simplicity and impossible-to-learn.
If you want easier, you gotta move to python/ruby/node.js, which are a joke in terms of performance and concurrency, with the last 2 being fairly young and immature (< 10 years).
Either way, the tooling sucks compared to Java, if not entirely non existent.
Entreprisey development is on Java because that's what best for them. Web development is on something else because they pick what's cool and trendy.
Initial release 2005. i.e. Young.
How could a 12-year-old framework not be old enough for you?
The issue is that you wont do good frontend in it (obviously), java programmers are more expensive, learning it well enough so that you can actually be that fast takes a lot of time and hosting is way more expensive. Learning language itself is easy, but absorbing necessary frameworks takes time. Moreover, people who are good in frontend prefer languages similar to javascript, because they already know it very well so those are easier to learn for them.
I think this is why it does good in enterprise - they have money to throw at hardware, most projects are under huge time pressure constantly and requirements change constantly. Frontend is super important for product, but not so much for enterprise.
After that, you'll understand that Java is on the spectrum of "easy" programming languages.
The IDEs are very helpful by the way. You only need one SDK, which is a single download.
But yeah, learning java ecosystem well enough takes a lot of time for someone who is just starting and you have to be comfortable with abstract thinking.