Everyday hassles in Go
crufter.com
crufter.com
I did some Go a year ago and liked it. Then coming back to it a year later after having done some functional programming in Clojure, it's not just the lack of generics that disrupt my flow but also having to think about all sorts of imperative programming details like naming and creating variables and scope placements in cases that would otherwise be unnecessary in a functional language.
I feel like I have been tainted by functional programming, and now Go feel like a joyless programming language which inevitably affect productivity.
I am looking for a replacement programming language to solve small problems for which Python/Ruby would have traditionally been used, good file system, stream and networking APIs, but concurrency as a first class citizen, garbage collected, fast startup time. Doesn't need to be good at long running programs, but it would be great if it can evolve to play a repeat role in a large system/process without the hassles of having to duplicate every written line of code into tests to prevent small changes from breaking things.
I am considering giving Haskell a try, but wondering if it might be overkill.
Any suggestions?
Using "sbt ~run" (re-runs your program automatically at every file change) may also be an option.
I'd like to be able to have a fast startup time when, say, running a script with its containerized environnement, when it is done the container exits, so I can't leave a vm running.
So I believe that leaves out JVM languages.
Independently from that, I did try Scala before Clojure and found its grammar to be overkill and unintuitive so it wouldn't be my first choice.
Other than that, I'm fiddling with LISP Flavored Erlang (lfe.io), but I've had previous Erlang exposure.
I am looking for a runtime which can take full advantage of multiple cores for a single program, something I believe Python would fall short at.
JS is still my tool of choice for building web apps. Just as for long running services, Clojure is a no brainer for me.
I am pretty open to ML or LISP based syntaxes, not necessarily looking for something Clojure-like.
There's a rule for academic papers where if the author is writing in clear language, he has something interesting to say, and if he's writing in fancy academese, he's probably saying absolutely nothing.
I don't really see a huge additional value for your proposed method call semantics, can you explain more? Some(Result) can make nil go away, but you've still got the error to deal with. So you're talking some type system where a single value encapsulates Result/Nil/Error?
Specifically, try reading these completely un-academic un-papers.
'Either' monad, with nice kid-friendly pictures: http://fsharpforfunandprofit.com/posts/recipe-part2/
Bonus: Martin Fowler, of all men, endorses the above approach, in Java, of all languages: http://martinfowler.com/articles/replaceThrowWithNotificatio...
A Kleisli category, with examples in down-to-earth, compact, un-hairy C++: http://bartoszmilewski.com/2014/12/23/kleisli-categories/
This stuff is not hard and is utterly practical. It can very well be used in current industrial languages.
The worst service you can render to yourself is to start thinking that you already know all what is worth knowing, and close your mind to new concepts. Guys that thought that Fortran and Cobol are enough to get by for foreseeable future were technically correct — both are still in some demand! Unfortunately, their market share has dramatically shrunk, deservedly, as have employment opportunities.
So, no, it's not just that the words are unfamiliar. It's that the problems that they're trying to solve are so abstract that they're completely disconnected from the work that I actually do.
And before you tell me that I'm really doing this stuff without realizing it: Maybe so. That doesn't mean that my life would be improved by doing it explicitly.
It seems to me that the "everything is category theory" approach has the same over-abstraction problem that the Java "AbstractFactoryFactoryFactory" approach has, and deserves to be as frequently mocked.
Still, it's worth knowing the name for a concept so that you can talk to people about it, and it's certainly worth knowing the library tools that exist and can save you time. Pretty often I'll write two or three lines of code, look at it, and then realise "oh, that's just traverseM" or some such. Even if it doesn't save me any time writing, replacing three lines with one library call is a huge bonus to maintainability.
I work primarily in embedded systems, where sequence is critical, many things are stateful, and there are multiple threads of control. So, for example, I could take the sequential aspects and re-write them as a monad. Or I could just do nothing, and let them be sequential imperative code.
I know that in a pure functional world, sequence can be written as a monad. But why should I care? In my world, it does nothing for me. I'm not going to try to write imperative code in a monad to build an illusion of non-sequential purity when the essence of what I need to be doing is so sequential.
Worse, composing the actions means I have to think through how the composing is going to affect the sequencing of events. Just having a function doesn't give me that problem - the sequencing is explicit, not hidden, and therefore is easier to reason about.
[Thinking...]
How about a robotic hand that's supposed to pick up a ball. The (C/C++) code might look like :
void pickUpBall()
{
openHand();
moveHandToBall();
closeHand();
}
You have to have that sequence. If the hand isn't open, moving it to the ball just bumps the ball away. If you close the hand before you move to the ball, you don't pick up the ball, you just close on air.So, in Haskell (if I had the hardware control libraries), I could put that in a monad to guarantee the sequence, and in an IO monad so that I could do things that had side effects like affecting the external world. But in fact I get both of those for free, just by not writing it in Haskell. (The pure functional nature of Haskell also doesn't do much for me in this situation. Yes, for the functions that can in fact be purely functional, it can make them easier to reason about and prove correct. But that isn't the difficult part of the code.)
Composability: It's unclear to me how you're going to compose something like this in any way that's a) useful in this environment and b) dramatically different from simply making a higher-level function that calls pickUpBall() and some other functions.
Actually it might be easier to use some open source embedded code.
For completeness sake, I'd write the snippet above almost identically:
pickUpBall = do
openHand
moveHandToBall
closeHandBut if that isn't a monad, then why did you write it that way? My original claim was that a monad wouldn't do much for me in my world, and if you wrote this without a monad, that kind of seems like you're agreeing with me.
You said that you'd gain composability from writing this as a monad. In this example, how would that work? What would it buy me that I couldn't write (as easily) using what to me is the normal way?
As for whether monads would help your code (and I still think they would) I'll need a larger example. In a small example abstraction and reuse aren't really apparent.
If you can provide a larger example, we can find out who's right ;)
Alternatively I'll try to find a larger example if you can't or don't want to.
But as you do, remember the question. We're in an embedded system, with lots of situations where sequence matters, and lots of stored state. The question is, what do monads buy me in that environment?
Anyway, I do have to say that the Monad abstraction is not that useful outside of Haskell because you kinda really need Type Classes to get monads "right" and most languages don't have that. What you might end up is with specific monads like promises in JS but then the monads are just a design pattern instead of being a concrete abstraction like they are in Haskell.
In addition to that, in Haskell monads are the only way to do sequential computations with side effects because by default the language is Lazy. This means that Haskell programmers have to learn this abstraction whether they like it or not while in other languages you can avoid it better :)
foo = [bar(x) for x in quux]
Or did you ever flatten a nested list?Congratulations, you've been using one of the most widespread monads.
Monads are not artificial constructs. Monads, like functions, or loops, or conditional statements, emerge naturally when you try to describe a computation, even a simple one. This is like speaking prose without knowing that.
The Haskell types often seem to think that the computing they do is the same as the computing that everybody else does, and therefore that the problems that they want to solve are the ones that everybody else wants to solve. That's not a very accurate assumption.
Note that the code I quoted above is Python, and I have lots of stuff like that in my mundane production code (processing files, tracking transactions, counting money, etc).
Seeing as Haskell is used for general computing (including embedded software) the computing is the same. I know what you ate getting at, but I don't think it is based on facts or anything rational.
foo = [bar(x) for x in quux]
This is effectively a map, so it's strange to call it "using monads". "Using functors" would be more accurate.You can get "monad" out of list comprehensions though, when you have multiple "for" clauses.
foo = [y for x in quux for y in bar(x)]
seems equivalent to foo = quux >>= barMonad is just a way to establish a (type verified) set of rules for a sequence of functions. This set of rules can be -for example- about the management of return value of these functions : executing the next function if we get a result, returning immediately if we have an error.
What is powerful with Monads, is their genericity. They can be applied to many different contexts, and still provide the same type safety.
You're likely already building very limited/specific monads without knowing it.
You just described Go, mostly. I can't think of a more fitting language given your stated requirements.
To your first point: I don't know what Go has to do with your preference of functional programming to imperative. Go doesn't claim to be a functional language so I just am not seeing the connection.
Let's say I prefer Haskell's syntax/approach. Coming from Go, what could I be missing in Haskell?
I think for the most part, you won't be missing a lot in Haskell. If you are looking for what you say above, Go should satisfy your needs greatly. Haskell is amazing for functional programming needs - fast prototyping and great at doing mathematical calculations. It helps you think in another way, but I certainly wouldn't use it for building something like an API or networking.
Try Nim, Ocaml, or Haskell to replace Go in that order. I order it in terms of probable familiarity.
Also, you get the full power of Visual Studio (they recently changed it to be completely free), which can really help with productivity in my experience.
You might also loose the ability to know how much memory and time a given task requires at most, which can be a problem when dealing with servers (it's much harder to know how many requests you will be able to serve per second or how long a given request will take to be treated, for instance).
"Pixie is a lightweight lisp suitable for both general use as well as shell scripting."
What gave the JVM a bad name regarding startup time are large JavaEE application servers which had dependencies to hundreds of MBs of libraries. In this case, startup is slow, otherwise: not so much.
But nothing's perfect. Some possible annoyances you might encounter:
* Much of the functionality required to be a valid "Python alternative" is not on the base packages. You'll have to install extra packages much sooner than with Python. And you'll have to know what packages to install.
* Despite having proper sum types, most I/O libraries in Haskell rely on exceptions for signalling errors. And Haskell has no proper stack traces. If you are not careful, you might find yourself pining for Go's multiple return values.
(Some would add "having to use the IO monad" to the list of annoyances, but I think it actually helps.)
But your point about needing to install extra packages stands for either language.
I don't feel this costs much, though, so even in quick&dirty mode, I feel that Haskell is quite productive enough.
Imagine every IO action having its own error type in a sum type. How do you compose them?
Composing the different error types wouldn't unify.
The best attempt I've seen in Haskell is the control-monad-exception[1] library, but I'm not sure how well that would interact with everything.
[1] https://hackage.haskell.org/package/control-monad-exception
Perhaps by converting each error type to a common sum type. Something like "Either (Either error1 error2) result". Typeclasses like "Bifunctor" are quite useful for massaging the errors around:
http://hackage.haskell.org/package/bifunctors-4.2/docs/Data-...
Asynchronous exceptions do pose a problem for this approach though, as they can pop up in any function at any time. And perhaps a fully sum-type-based approach would clutter function signatures too much anyway.
You may want to have a look at ClojureScript, which has come a long way in the last few years. While the tooling is still not as nice as Clojure-on-the-JVM in my opinion, it continues to get better everyday--this week support for a better REPL via NodeJS[1] was introduced, for instance. A lot of smart people are putting good effort into the compiler and ecosystem and I suspect we'll see a continued uptrend in cljs adoption. Anyway, it may be just what you want: an elegant, productive, pragmatic language with first-class support for concurrency (atoms, core.async) and well-suited to short-lived and scripting-domain problems. (Bonus you can write your frontend and backend in the same language if you want!)
[1] http://swannodette.github.io/2014/12/29/nodejs-of-my-dreams/
After having implemented a mini web services in go and being fed up with its limited type system, i decided to stop coding in go and start recoding my project in java using what is often advertized here as the most minimal framework : dropwizard.
Well, i downloaded the framework, configured maven, started the hello world tutorial, and quickly found myself dealing with 2 xml files, 1 yaml file, multiple classe files ecerywhere, and couldn't get the whole stuff work immediately.
Then i realized that my golang implementation was already working, and that i completely understood my code and its potential performance characteristic, without having to have deep knowledge about the jvm internals, things like sevlet containers, or how the whole stack would deal with my global variables in a multi-threaded context. All my code was just one simple program.
My temporary conclusion right now is that golang type system is indeed extremely shite, but that if what you need is to develop simple yet efficient web services, it doesn't matter that much.
No it doesn't use magic, but it does take a language that is burdened with a complex type system and throws it out the window.
You also are glossing over a huge host of complexity around build/deployment. What jvm are you targeting? Is it on the servers you are deploying to? Are you going to make a fat jar? If not, how are you doing dependency resolution? Is your build artifact something that sbt does out of the box or slightly different (lord help you if it is)? Are you going to do ivy, s3, or maven resolution?
All of that complexity falls away with golang and this is a conscious choice of the golang team. I have lots of things I love about Scala and lots of things I hate about golang, but from a "get simple web service out the door in a scalable way" golang wins hands down.
What are you talking about? Spray works hand-in-glove with the Scala type system; would, in fact, be impossible in a language without it.
> You also are glossing over a huge host of complexity around build/deployment. What jvm are you targeting? Is it on the servers you are deploying to? Are you going to make a fat jar? If not, how are you doing dependency resolution? Is your build artifact something that sbt does out of the box or slightly different (lord help you if it is)? Are you going to do ivy, s3, or maven resolution?
If you're just playing around, you push the button in your IDE and it runs. If you're a serious business you make these decisions once and reuse them in every project. (To answer your questions explicitly: the default is to build for java 1.6, which has been on servers since before go even existed; if you're building for a newer version it's because you know what you're doing. My personal choice would be the maven appassembler plugin, but using the shade plugin to make a fat jar works fine too. I wouldn't touch SBT, it's too complex).
Yes, you do have to make some choices, put a bit of effort into deployment. But I think that's a necessary cost, because it's the only way to allow tooling to evolve. Imagine if ant had been built into the JVM back in 2000; maven would never have been able to replace it, so we'd be stuck with it forever. Look at the Python standard library; when Python first got big, it was this great selling point full of really useful tools. Now, it's where modules go to die, because things that are built into the language can't evolve at the same place as things that are outside it. I fear exactly the same thing will happen to the Go tooling (though of course, we won't know one way or the other for ten years). If I can think of one language that really emphasised an "easy deployment model", it's PHP. Not only did that approach lead to terrible deployment practices, but now that the landscape has shifted and Apache is no longer universal the way it once was, it's not even particularly easy to deploy PHP any more.
In the example you linked to: https://github.com/spray/spray/blob/release/1.1/examples/spr...
The "main" function dispatch is not type safe (nor can it be given Spray's reliance on the current akka type model).
My experience is that anything that relies on akka ends up throwing out a huge majority of useful type safety because it is hard to minimize it. A bunch of the advantages you mention around AOP and not monkey patching are due to this.
The configuration problems you mention are the result of these libraries' need to be compatible with older versions of Java.
If you write your own data structures in Java, there is nothing preventing you from fully utilizing Java 8 features. It makes everything unusable in Java 7 of course, which is not considered an acceptable sacrifice by Oracle and it won't be for decades.
The same is true for C++. When it was designed it was a magnificent advance (and it hasn't fallen for the particular "enterprisy" traps java has fallen for). But then the language was redesigned, and redesigned. RTTI being the first major clusterfuck that they decided to make partially binary compatible. Exceptions being the second, and now lambdas being the third. You may disagree on what exactly is a clusterfuck and what isn't, but I hope you can agree on the deeper problem : baggage. Various things matter more to other developers (like pthreads, winsock, ...)
Writing a program from scratch in C++11, not using any of the old libraries ... is almost as pleasant as using rust. But most libraries can't update, because they'd lose too much doing so.
Go is on it's first design of it's standard library, and everything works reasonably well with today's standards ... this is of course not so much a feature of the language as it is the result of when it was constructed.
When the winds turn again, and they will, Go will look as antiquated as Java and C++. Actually, given how well C++ has handled this in the past, it will probably look more antiquated than C++.
A couple of areas where it really falls down though are that Java 1.0 lacked the emphasis on tooling which is golangs greatest strength and added a goal of "write once/run anywhere" which at the time seemed very important but now adds a lot of baggage.
1. Dropwizard is not a minimal web service library, it's a minimal web service framework. You could've used just a library, for example this[0] one, and be done in 10ish lines boilerplate that you fully understand.
2. That would also mean you wouldn't need Maven or any XML configuration. Just a bash script or a Makefile that invokes your compiler.
3. There's 3 ways to deal with global variables in the world. The first is plain allow them, and let everything go to shit when two threads access them at the same time. That's how most languages, including Java, do it. The second is allow them, but never allow more than one line of execution at the same time, this is how many scripting languages like Javascript and Ruby do it. The third is 'not' allow global variables at all, and instead only expose shared state through mechanisms that explicitly deal with concurrency like pipes/messages/locks, this is how Haskell and other pure languages do it. (There's also STM, but that's a bit more complex)
Once you know which of the three your language does, you'll know exactly how to deal with it. No need to be uncertain about it.
0] http://docs.oracle.com/javase/7/docs/jre/api/net/httpserver/...
Minimally, just drop the binary and an init.d script. Feel free to use something fancier for manageability etc., the point was the simple option is available.
Go is definitely simple. But I wouldnt characterise modern day Java development as being that complicated.
Another big thing is that same app written in Go just takes (significantly) less RAM than when written in Java. Go has arrays (and slices) of structs, not just struct pointers (in Java-speak: object references). This also dramatically reduces short lived garbage. Additionally Golang strings consume less memory, as they are UTF-8 by default in memory. In Java, you can start JVM with -XX:+UseCompressedStrings option, but that adds branches and still falls back to UTF-16 when string contains non-ascii characters.
We deploy all our services as uberjars, we have a single Amazon AMI with JDK 8 installed. It's pretty frictionless. It goes from a simple Gradle project, to a single jar on S3, and pulled from S3 by our AMI. Automatically launches via an upstart script.
I'll give you less RAM usage for sure. But when I'm launching a whole VM for a service anyways, I don't care that much. I tend to lean toward Go when I want a low-footprint coprocess for another service, or other small apps.
Integer[] boxedInts = new Integer[5];
You're allocating an array of references with length of 5, so usually 5 * 4 + 8 bytes. Additionally, each of the referenced Integer Object instance take up 16 bytes. Multiple arrays (or reference fields) can refer to same Object instance.The example was about Integers, but it's basically the same story for FactoryWorkerPersons.
Java doesn't have arrays of objects. Only arrays of elementary types, up to 2^31-1 entries.
And sure you only need to replace one binary. Java can do that too but also has the flexibility to hot deploy new code/libraries so no costly outage.
Give me ssh, and I can deploy my app to default installation setup in a second, like CentOS 6. That includes binary transfer, installation and startup.
Hot deploy is nice. Sadly Golang is one of the only language environments where you just can't do it. Do watch out, the issues you'll encounter may be rather unique. Especially when applied in production...
The only way a generics (however you choose to define it) will be added to Go is if someone puts together an in-depth proposal about how it works, beyond just syntax. This means how it will work with the compiler/linker. How it will work with the runtime and GC.
Due to the amount of time it will take to put together such a proposal without the guarantee that it would be added, no one has gone down that path yet. You also need a deep understanding of many parts of the language to be able to produce such a proposal, which is another barrier.
[0] https://groups.google.com/forum/#!topic/golang-nuts/L-2OTItS...
[1] https://groups.google.com/forum/#!topic/golang-nuts/smT_0BhH...
Generics may be overkill. Parameterized types may be enough. The difference is that you have to explicitly instantiate a parameterized type. Generic functions and classes get instantiated implicitly when used, which gets complicated. (See C++'s Boost.)
Go already has built-in parameterized types - "map" and "chan". Those are instantiated with "make", as with
p := make(map[string]int)
That's an instantiation of a parameterized type in Go. Go already has a full parameterized type mechanism. It's just that users can't define new parameterized types - all you get are "chan" and "map".For parameterized types, struct definitions would need type parameters. A parameterized type would later be instantiated explicitly in a type declaration, with the parameters filled in and a new name given to the generated type. This is more explicit and less automatic than generics. There's no automatic specialization, as in C++.
This is enough that you can write, say, a binary tree library once and use it for multiple purposes. It's not enough to write C++'s Boost. That seems in keeping with the design of Go.
Similarly, if you're going to build a binary tree data structure, then you probably need a way to compare elements. How do you express that constraint?
Standard ML has a similar limitation, which resulted in the presence of an `eqtype`, which is basically a way of saying, "any type T that can be compared for equality." But of course, ML has its modules...
So I'd argue that type parameterization alone doesn't really buy you much. You might say: well, we need bounded polymorphism then. OK. How do you define bounds? Maybe you figure out a way to abuse interfaces (but a bound isn't a type while an interface is a type), or you add a new way of specifying interfaces to the language.
The road to generics is long, complex and fraught with trade offs. Blessing a few key parametric types is a really interesting compromise, and I believe it's one that was absent in a similar practical manner in pre-generics Java and C++.
If we needed to write, say, "thread-safe map", or "network chan", with type parameterization, that should be possible.
I find it interesting when people wax poetic about how one language is better than another and how if we just did x, y or z then we'd have this perfect solution.
That said I appreciate the author's write-up of some challenges with Go. In the end the reality of any "product" is building what the user's need not what they ask for. That said I do worry that the Generics argument seems to be slowly approaching a religious war that will distract people from the other enjoyable aspects of Go. Worse yet it may calcify the Go development team in a way that will keep them from addressing the basic issues that generics might help solve. Either way Go is just another computer language and I'm happy to use it often to solve problems I'm working on. That said I use a lot of computer languages every week when I'm working on stuff, none of them are perfect and my solutions to the underlying problems are fragile and constantly evolving as the problems unfold. This is the nature of our business, to constantly do battle with poorly understood problems using imperfect tools in a world where we are fooled into thinking everything is black and white because at the core of our technology everything is a 0 or a 1.
"One Computer Language to bring them all and in the darkness bind them".. LOL
There is scope for legitimate criticism of language design based on the expressive power^1 of their features.
In this case, generics would make Go strictly more expressive, as, without it you must write O(n) more code or perform a global refactoring to simulate it.
1. http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.51.4...
When programming in an intentionally restricted language, the question is whether the limitation in power is put to good use in some other way.
Because generics are important.We're not talking about crazy C++ templates here but a more rigid feature that would still make Go language more expressive.
Or why expose this interface {} feature and allow people to use it in a statically typed language?
People who chose Go obviously want type checking (along with CSP that makes go so awesome), or they would be using something else.
As a former C# developer I would agree that life got easier with the introduction of generics, but you can develop the exact same software with and without generics.
Generics can drastically reduce the time required to implement features because less time is spent on re-implementing (nearly) identical algorithms for different data structures and more on simply using them to write something that works.
The cost paid for this is usually in the form of minor efficiency hits because data-specific efficiencies aren't exploited.
Also, consider me complaining about C's lack of generics. It is in fact the primary reason I prefer C++. If C had robust generics with reasonable syntax I'd strongly consider using it instead of C++.
For the kind of work we do in 2014, yes.
People who would complain already know not to bother using C.
And as somebody who developed in Java and C# before generics, those languages were awful before those features were added.
`interface{}` isn't anything special, or something akin to `void*`. Interfaces are a core part of the language, and `interface{}` is just the literal form for an "interface with no methods", which happens to match anything since all types can fulfill an empty method set. It's no different than using this `Something` for exmaple:
type Something interface {
}It however does pass the type checking to run-time instead of compile-time, which is a trade-off that some feel is worth the language simplicity.
That's absolutely still loosing type safety. Reflections don't substitute type safety.
I also wouldn't consider Go type assertions to be in the same class as reflection. A type assertion is the way you extract and check the underlying type from an interface, and is quite common even when using more complex interfaces. There's also no way to make use of the underlying type from an interface without first asserting it as that type.
I think this is a mistake. A lot of business is run this way, though.
Isn't that extremely sad though? We finally have a perfectly precise tool, yet we keep building gooey piles of uncertainty on top of it.
Sure we're just imperfect humans, but that is what compilers and tooling are for - to point out our mistakes and help us get it right
I'm hoping that in the next 5-20 years Rust and Haskell (and later Idris) will change everything.
Unfortunately, they won't. These problems are not a 'bug' of current languages, but the inherent nature of the problem. At some point, the perfectly precise world of computers has to meet the messy world of user requirements. As a programmer, my job is to translate one into the other, within the constraints applied by whoever is paying the bill.
For example, the behaviour of null/nil references in most typed languages mentioned in the article has nothing to do the inherent nature of messy user requirements. Same goes for the lack of sum types: infact they're perfect for modelling messy requirements, much better than simulating them with structs and forgetting a check somewhere (I'm just parroting the article here)
These issues aren't caused by messy requirements but by messy leftovers from legacy languages. Thats fine too, nobody expects us to get it right the first time: but that was 40 years ago and repeating the same mistakes in languages made in 2009 feels really sad.
For example, one of the biggest problems with building websites ten years ago was that you had to slice everything into tiny images because of table-based layouts. Now we've got CSS3 with SASS and Compass and that's no longer a problem. But whereas then, 90% of users were using IE6 on a 15 or 17 inch monitor, now my users are on a multitude of browsers and screen sizes.
Haskell is great at concurrency and parallelism not because the language was designed around those at all - but because the language is well designed, and focuses at communicating the programmer's intent, rather than implementation details. This intent is more directly translatable to correct concurrent code than an imperative spec which is inherently lower-level.
Whoever designed Go seems to have some experience debugging and maintaining large concurrent systems.
When you design a language, you have to be so careful of things you add. Because otherwise you get C++ (or one of those cute let's-add-all-the-features-we-can-think-of scripting languages). In good and in bad. You can't remove features afterwards, so better not add a feature you can't get right from the beginning.
The issue is more the dismissive attitude towards them.
Indeed, they are useful. I use them in Java every time I find a good reason to do it. So, I should say in another way: I don't miss generics in Go.
Given my experience [0] in Go in the last years, I realized that if you miss generics in Go, your code is trying to cope with too much.
I didn't miss them when I developed Mergo [1], a simple library to merge structs. I think we all agree if we should rewrite this in Java (or other language with similar type system) we would use generics at some point.
I have two years of sustained Go usage (since end of 2012) and never missed them.
Was wondering what you mean by that (not disagreeing just interested)?
The rest is mostly a Haskell fanboy whining that Go isn't Haskell.
Generics by author-template sounds exactly like what the Go authors would want: source code is still shared (rather than binaries), no Make/Include/Macros for the build, and no hassle for the _user_ of a package.
> Also, if the containing package is intended for import by go get, once the file is generated (and tested!) it must be checked into the source code repository to be available to clients.
Moving external processes into the source code is characteristic of how they've developed Go.
A GCO/SWIG style generator built-in to `go` would be a great next step. It wouldn't have to be perfect, just deal with includes and defines separate from Go build. Even if the output needed to be hand edited it would be a great starting point.
"Code reuse!"
Cracks me up every time. ;-)
We fall for those words over and over again. But in reality, sadly, reuse almost never happens. Maybe one day, maybe even in the next project...
Lots of code reuse there that depends on generics.
Even in the process of writing a single program, there are a number of times where you reuse certain bits of logic, and having generics makes it easier to refactor those into a single function, instead of duplicate code with multiple types (or overly generic types).
I don't know in what industry you work for. In the IT industry code reuse very much happens every time.
It might not be using code from your previous project to build the next one (through people do that ALL the TIME), that it's very much using common libs for thousands of projects.
The kind of code reuse he talks about has been working perfectly fine for ages. E.g a generic sort, filter etc function, instead of having to handle the basics every time.
But generics? "Safer" type system? No, that's unnecessary complexity. As others pointed out, we already have Haskell and Rust for all those things.
That being said, it is well known that Golang's present GC performs poorly. It presently fails to free memory in certain cases, and has pretty high overhead. There is work done on improving the GC to be fully concurrent, but that is still probably still 2-3 major versions away.
Actually, the Go team is making very good progress on all the prerequisites to real-time garbage collection. Who knows if they will manage to get all the way there, but I know there's been some discussion about it on the mailing list.
If you look at how fast the GC has improved over the years, it's kind of ridiculous that anyone bothered to criticize it at all, as if it was not really the temporary solution the devs said it was, and was in fact going to stay shitty forever.
It's so easy to add/delete imports with GoSublime plugin.
Everytime I write Java now, I seem to end up with loads of unused imports as I prototype and it just feels messy.
Once you get used to it, it's kind of nice.
Even 1ms pause ceilings drive people to non-GC options, so I think the "game changer" number is much lower than that.
For interactive desktop applications, the pause should be adjustable or at most a few milliseconds. Say, maximum 5 ms.
For hardware devices... well, you just don't use GC there in the first place. Microsecond is a pretty long time in that area.
For compiled regular expressions: just use PCRE library for compiled regular expressions. I bet there's already some library that does it for you.
Maybe you should take a look at Lua and especially LuaJIT [1]. You might like it. Lua(JIT) is single threaded, but provides co-routines [2] as a language construct. Performance is about same as Golang, sometimes faster, sometimes a bit slower. Lua has GC, but you can control latency by controlled GC invocations. It can be made pretty predictable, a lot of current AAA games use Lua internally.
LuaJIT's FFI [3] is excellent, calling native C libraries is a breeze, very easy and requires no bridge libraries or code.
[1]: http://luajit.org/
By compiled regexpes, I meant replacing things like bytes.Equal(foo, "qwe") with things like foo.m(`^qwe$`) or even foo.m/^qwe$/ that have the same performance. All the loops that scan through slices could benefit from it, golang has a lot of them. Plus more overall matching/scanning consistency, that should lead to fewer mistakes.
I think that threads are easily up there with null/nil as one of the worst CS mistakes. Communicating via shared variables at the application program level, vs an IPC function / channel, seems like a noose that should be designed away from reach.
There is nothing exponential about nub. Given only equality comparisons, detecting and eliminating duplicate elements requires quadratic running time.
type JSON map[string]interface{}
Go ahead and use the json.RawMessage type instead:
http://golang.org/pkg/encoding/json/#RawMessage
Not even going to wade into the lack of generics war, still very happy without them and going on a full year of Go usage.
If it doesn't, then you just use json.RawMessage to defer unmarshalling, but you can still dispatch to a finite number of structs that represent the universe of possible responses.
[0] And no need to write it all out by hand - if you have a single example JSON response, you can generate it automatically: https://github.com/ChimeraCoder/gojson
Maybe it's because I've never felt the need to use generics and in all these articles the examples they give are functions of a few lines that would be quicker to write 2-3 times for different types than remembering generic syntax.
Maybe it's because they exalt one line functional functions over a nice, simple, easy to read FOR loop when the former are so difficult to read, figure out what they really do, what is the performance cost, debug...
Maybe it's because of the bogus examples they give like criticizing
> file, _ = os.Open("file.txt")
> file.Chmod(777)
for not handling explicitly a possible error when it's just because the example is ill written and the proper code is
> file, err = os.Open("file.txt")
> if err != nil {
> ... handle error ...
And here I stopped reading the article, it's always the same arguments over and over: more elegant and complex code vs. the un(cool) but oh my, so much simpler Go code.
What I find really amusing though it's how people are so smug in their writing, pointing out the "obvious errors" (billion dollar mistakes!) that the Go authors made and their "ignorance" of proven modern programming language constructs they could implement in Go.
There are two possibilities here, pick your preferred one.
1) Pike, Thompson & co. made obvious errors in designing Go because of their ignorance of programming languages and/or ineptitude
2) These bloggers claiming obvious errors in Go design don't really fully understand the trade-offs involved in what they ask for and ignore the fact that the Go authors have carefully thought about them and optimized accordingly
I will go back to programming in Go, I'd take writing for loops all the time vs. writing a one liner in Haskell and then agonize over using the lazy or strict version of it :)
Golang seems to shine at very simple, concurrent tasks that can be passed from one set of developers to another regardless of sophistication levels of those teams. It seems to fail pretty miserably at making a single sophisticated developer vastly more productive. It's what Java would have been if they'd thrown away the write once/run anywhere goal and never gone down the J2EE box canyon.
There is nothing wrong with either goal btw, they just are in some ways opposites.
> file, err = os.Open("file.txt")
> if err != nil {
> ... handle error ...
A type system with generics can use types to make an error a different type and produce a compile error if you don't handle the case without making the code more complex.Uhmm, what? That seems entirely ignorant for ignoring an expressive concept and rather "do it the hard way". And, "remembering the generic syntax" is no different from remembering any other features' syntax, and I don't imagine you have issues remembering those, do you?
Go was designed by a team at Google, where there is a policy that the only languages you're allowed to code in are C++, Java, and Python. I think the resulting language reflects that; it has many of the strengths of C++, Java and Python, and might even be a better language than all three. But it is also missing really obvious improvements that could be taken from e.g. the ML language family - which shouldn't be surprising, given that no-one at Google is allowed to use those languages!
Why do you even need `for` loops? Comparison + labels + jumps are good enough. You want to know the reason? It's easier to understand the intent of the programmer when there is a loop. `for i = 1 to n do ... done` means that the programmer wants the body to be executed n times. `while not (list.is_empty()) do ... done` means the body should be executed as long as the list isn't empty. If those are acceptable, why not also abstract away applying an operation to a collection, or removing elements from a collection, etc.? Basically it boils down to: I like Go, and everything it has needs to be there, and everything it doesn't have is unnecessary complexity.
The amount of for loops you will write will also makes it obvious when you start doing O(n^2) operations in your methods. Same can be said with variables declaration and error verifications in Go. They are annoying at first, but then you understand by reading your code the places where error can occur or where you decided to ignore errors (By using _).
It's a different approach and it's refreshing.
The longer the piece of code one has to read, the easier it is to read it incorrectly, miss a bug, or introduce a bug.
x = x + 1
foo[x]
But easier to miss a bug in foo[x++]1) You cherry picked one of the most confusing uses of operators (and sources of bugs) that we have. If it weren't so ingrained in our educations/history (and so useful for old fashioned looping semantics) I think we all would have moved on from ++ syntax a long time ago.
2) As flawed as it is, it is a well studied phenomenon that LoC is nearly the only measurable statistic we have that is predictive of error rates (that is higher LoC solutions tend to have higher bug counts).
3) The bigger point about looping constructs isn't about LoC in any case. It is de-coupling 2 different areas of code responsibility. One is the mechanics of iteration and the other is what to do on each event.
At the end of the day most looping constructs end up being sugar around while loops anyway, so the reductionist argument is all of it is unnecessary complication, just do everything in a while loop.
I don't know why people keep saying this. We write code to solve problems. The best code solves the most problems in the most correct and efficient ways.
Regardless of your measure of efficiency, if someone cannot quickly tell if your code is correct, does what is intended, and does or does not apply to their problem, then the code isn't good.
That's your measure: Can the people who want to solve the problem you are trying to solve efficiently identify, evaluate, and employ your attempted solution?
The reason people keep saying that we don't have any measures that do this, is that it is a bafflingly tricky problem that has been studied for decades with hardly any forward progress, and lies at the heart of most of the big issues in our industry.
>The reason people keep saying that we don't have any measures that do this, is that it is a bafflingly tricky problem that has been studied for decades with hardly any forward progress
And what if I had a solution? How does this comment help anything? What do you think the difference is between unsolved and unsolvable, and how does your reaction not foolishly apply to both? How would you recognize progress in the context of my post?
Here's a Haskell factorial function:
-- Basic factorial function.
fac :: Integer -> Integer
fac n = product [1..n]
Here's an attempt at a factorial function in another language: .r $15265f38a [] (44) ; dup n + offset;
_
Which is better code? Do they both compute a factorial? Do they both terminate? How did you determine this without using any 'measurable' data?What I'm getting at is that determining which of 2 pieces of code are better is one of the biggest open question in the computer science/software industry. Further, it has been widely studied for a long time and across a wide variety of measurements and contexts, yet remains an open question.
If you have a measure that can be systematically reproduced by anyone for any 2 pieces of code that purport to solve the same problem, that would be a huge breakthrough worthy of wide publication and probable riches.
If you can make that machinable (even given caveats around provably impossible things like the halting problem) you could literally transform the entire industry.
If that is your standard, then it is highly impractical, but not theoretically meaningless. However, "determining which of 2 pieces of code are better" is not such a problem. It is one that can be solved by heuristics and approximation, just the same as predicting the outcome of a race can.
So while there is as of yet no perfect algorithm, I would strongly disagree that there is _no_ algorithm or that there is no way to compare different algorithms meaningfully. We have measurements, and just because they are not perfect doesn't mean they cannot be verifiably correct within a useful margin of error.
We can reliably tell which two pieces of code are better. We cannot do it with extreme precision, and we cannot do it quickly enough to make predictions, but this does not mean that the concept of 'better code' is invalid or not understood in a productive sense. It certainly doesn't mean you can't make informal arguments from it.
Unless you need a do/while loop. Then you're SOL.
Either way, a very good reason for not using loops is to make code more readable. If I see a loop I have to run it inside my head to figure out what the intent is, and if there's multiple things going on, that can take time. If I see a 'map' I think "right, this is tranforming every element in this list in this way"; if I see a filter, I think "right, this is removing elements in this list that don't satisfy this property", if I see a groupBy, I think "right, this is grouping elements in this list by this property", etc., etc.
Code isn't only more succinct, it's more human friendly.
Choosing to write more verbose, difficult to decipher, difficult to maintain code, under the claim that it will perform better, is not a good thing: "premature optimisation is the root of all evil".
In practice, if this becomes an issue (which you find out through benchmarking once you know there is a perf issue), most modern languages offer an easy way to swap out your data-structure for a lazily evaluated one, which would then perform the operations in one pass. Languages like Haskell or Clojure are lazy to begin with, so do this by default.
Your Big O analysis is correct. However, in a real-world case the list could be an iterator over a log file on disk. Then you really don't want to use chained combinators, repeatedly returning to the disk and iterating over gigabytes of data on a spinning plate.
And yeah, you could benchmark to figure that out, or you could use the right tool for the job in the first place.
Machines in Haskell: https://hackage.haskell.org/package/machines
Scalaz Stream in Scala: https://github.com/scalaz/scalaz-stream
I've no doubt an equivalent exists for Clojure, too, although I'm not familiar enough with the language to point you in the right direction.
One of the most amazing things about writing the IO parts of your program using libraries like these is how easy they become to test. You don't have to do any dependency injection nonsense, as your combinators work the same regardless of whether they're actually connected to a network socket, a file, or whether they're just consuming a data structure you've constructed in your program. So writing unit tests is basically the same as testing any pure function - you just feed a fixture in and test that what comes out is what you expect.
I found this really useful when writing a football goal push notifications service for a newspaper I work for. I realised that what the service was essentially doing was consuming an event stream from one service, converting it into a different kind of stream, and then sinking it into our service that actually sent the notification to APNS & Google. The program and its tests ended up being much more succinct than what I would normally write, and the whole thing was fun, and took hardly any time.
I would say that is the right tool for the job.
This started as a conversation about for loops vs. chained combinators and wound up with specialized libraries for IO. You're not wrong, but that's a lot more work than a for loop to parse a log file efficiently.
The value of using map is precisely that it can't do everything a for loop can. It can only do one simple thing, which makes it easy for the reader to understand what it's doing.
Because a for loop is the most appropriate construct in the given language. That's different than comparing hypothetical constructs that a language doesn't have.
Go has higher-order functions, and you can write all the maps funcs you want, it just doesn't have the ability to create generic combinators with parametric polymorphism. That's a trade-off I accept with Go to get things done. Like a lot of people, Go for me hits a sweet spot of simplicity, productivity, and performance; simply put the benefits outweigh the drawbacks. Whatever, it's a programming language; a tool.
It's like complaining that a functional language doesn't have an easy way to write mutating procedural code. You're always working within the confines of some language, and unless you're directly working on that language's implementation where you can change things, I'd rather be working with the language than fighting it.
> It's like complaining that a functional language doesn't have an easy way to write mutating procedural code.
I do complain about such languages. That's why I use Scala.
Not having generics means we can't properly build these ourselves, and Go doesn't offer list comprehensions, so instead we're forced to for loop.
I used to play with a small plastic hammer when I was in
kindergarden thirty years ago!
I will be very irritated if people laugh at me for trying
to build my new house with this hammer now.
-- GolangersThis compiles:
package main
import "errors"
func main() {
f()
println("hello erroneous world")
}
func f() error { return errors.New("boo") }This compiles:
package main
import "errors"
func main() {
x, err := f()
if err != nil {
return
}
y, err := g()
h(x, y)
}
func f() (x int, err error) { return 1000, nil }
func g() (y int, err error) { err = errors.New("boo"); return }
func h(x, y int) { println(x/y) }
(I do write Go most days and rather like it, but apologists for its weak parts are legion)In any case, the go compiler is very helpful in most cases, and better tooling can improve things further.
The tradeoff in these designs are to prevent tuple types, always keep using structs, prevent using algebraic types over structs. Allow for nil, but try prevent common Null errors etc.
Haskall is theoretical a much better language. But Golang was designed to be practical & simple over being mathematically sound, and theoretical better.
I'd like to see a citation for this as I've never seen this as a stated goal of golang. In fact, if it were the stated goal that is one of the most damning arguments against golang as it very clearly fails at this task.
I've been under the assumption that golang was designed to make writing/deploying/managing simple web services across a wide variety of machines and across a wide variety of teams. While I'm still reserving judgement on it accomplishing this goal, I'd say it is much more likely to accomplish this than trying to have similar power to python.
"Go compiles quickly to machine code yet has the convenience of garbage collection and the power of run-time reflection. It's a fast, statically typed, compiled language that feels like a dynamically typed, interpreted language."
That comes reasonably close.
The following produces a single binary with native code generation via compilation to C, no VM:
import rdstdin, strutils
let
time24 = readLineFromStdin("Enter a 24-hour time: ").split(':').map(parseInt)
hours24 = time24[0]
minutes24 = time24[1]
flights: array[8, tuple[since: int,
depart: string,
arrive: string]] = [(480, "8:00 a.m.", "10:16 a.m."),
(583, "9:43 a.m.", "11:52 a.m."),
(679, "11:19 a.m.", "1:31 p.m."),
(767, "12:47 p.m.", "3:00 p.m."),
(840, "2:00 p.m.", "4:08 p.m."),
(945, "3:45 p.m.", "5:55 p.m."),
(1140, "7:00 p.m.", "9:20 p.m."),
(1305, "9:45 p.m.", "11:58 p.m.")]
proc minutesSinceMidnight(hours: int = hours24, minutes: int = minutes24): int =
hours * 60 + minutes
proc cmpFlights(m = minutesSinceMidnight()): seq[int] =
result = newSeq[int](flights.len)
for i in 0 .. <flights.len:
result[i] = abs(m - flights[i].since)
proc getClosest(): int =
for k,v in cmpFlights():
if v == cmpFlights().min: return k
echo "Closest departure time is ", flights[getClosest()].depart,
", arriving at ", flights[getClosest()].arrive
Statistics (on an x86_64 Intel Core2Quad Q9300): Lang Time [ms] Memory [KB] Compile Time [ms] Compressed Code [B]
Nim 1400 1460 893 486
C++ 1478 2717 774 728
D 1518 2388 1614 669
Rust 1623 2632 6735 934
Java 1874 24428 812 778
OCaml 2384 4496 125 782
Go 3116 1664 596 618
Haskell 3329 5268 3002 1091
LuaJit 3857 2368 - 519
Lisp 8219 15876 1043 1007
Racket 8503 130284 24793 741
Not only is this syntax far more approachable for anyone who likes Python, as opposed to Go, Nim is actually suitable for systems and embedded programming. Optional GC and manual memory management makes Nim one of the few up and coming systems programming language that ventures into C territory while still being safe and pragmatic enough for general usage.Generics are not a particularly new idea and nobody who uses language that implement them yearn for the days when they weren't around.
See Paul's post on the idea that 'worse is better': http://pchiusano.github.io/2014-10-13/worseisworse.html
You can make your language "unsound" by violating those mathematical principles, or you can make something like the I/O monad. It is indeed "soundness" vs "practicality".
EDIT: Oh, and that link seems to have misunderstood the 'worse is better' philosophy -- C++ isn't the champion of 'worse is better', C is.
I think when you get to more complicated (or at least alien) abstractions like the I/O Monad, it's not about whether it's practical to build software with it. It's perfectly practical - I know people working at big investment banks writing large scale software in Haskell, and they don't have any issue with it. What it comes down to is whether the people you work with will understand or want to invest the amount of time required to learn those abstractions. If you're working with very talented programmers, that might be yes. With the majority of programmers it is probably no.
But we're not talking about the I/O Monad. We're talking about generics, which are not difficult to understand, and that Go lacks them is a shame, as it means that whole layers of abstraction are not available to the programmer, so they end up having to spend more time writing boilerplate.
The problem with being in such a rush to confirm your own worldview is that you don't learn anything from anyone else's. The 'worse is better' philosophy is about valuing simplicity over completeness, specifically in the context of UNIX/C vs Lisp machines back in the 80s. How do you think most of those people feel about, say, the STL?
C++ has a problem with bloat and 'tacked on' features, and that is clearly 'worse is better' design. Backwards compatibility is a huge indicator of this kind of thinking. Partial backwards compatibility is an even more egregious example.
The good news is that in 1995 we will have a good operating system and programming language; the bad news is that they will be Unix and C++.
In his later essay on the same topic (http://dreamsongs.com/Files/IsWorseReallyBetter.pdf) he again talks about C++: In the computer world, the example of interest to JOOP readers is C++. Many would concede that languages like Smalltalk, Eiffel, and CLOS are vastly “better" in some sense than C++, but, because of its worse-is-better characteristics, the fortunes of object-oriented programming probably lie with C++.
Simplicity is more often simplicity of implementation than interface. Anyone who's worked with the C standard library knows that it's anything but simple. There's a lot of incidental complexity that is due to design inconsistencies. The complex parts of a language like Haskell are the bits where the ideas themselves are complicated, but the power you get for overcoming that initial learning curve is very great.Programming languages should be kind of boring, and support the average developers' work. That is why Haskell will ultimate fail and fade away - most of the developers on this planet are simply not skilled enough to touch any of that stuff. Attempting to add all Ninja concepts to a language will just lead to a massive failure.
Go does a very good job on forcing even the most average developers to reach surprisingly succinct and elegant solutions. However, the lack of generics really forces in some situations to either use the reflection or stop being very DRY or elegant. Go wouldn't have to implement full generics to fix that. Just adding something similar to what interfaces are to methods for variables would do, without the rest of the moving parts.
Furthermore ... Go ... succinct ... elegant ... using reflection as an alternative to generics ...
In my experience Go is neither succing (if err { ... } one-statement if err { ... } one-statement if err { ... } one-statement). It is extremely inelegant because due to the confused sequence of statements it isn't possible to keep your thoughts on what the method is doing, as there are constant error handling concerns everywhere (or regrets/TODOs about error handling to be completed in the future ...)
Is this
(if this doesn't refer to itself or the sentence it's in, return that information to the caller manually so he can ignore it)
somewhat
(if the above statement is not and adjective you have a problem with your understanding of english, report to the relevant authorities)
unclear
(if un doesn't result in the reversal of the meaning of the word clear, we have a problem and should inform the main sentence interpretation routine that understanding is flawed)
perhaps
(missing error checking)
?
(if the above is not punctuation, report back that your browser is probably not using the correct character set)
Using reflection as an alternative to generics is horrible in lots of ways, but just mentioning a few : no type safety (not even a decent verification you're passing the correct number of arguments). It's extremely long code, full of edge cases (such as arrays being a value type and slices being a reference pointer despite differing only in a single character). And it's sloooooooooooooow. As in ruby slow.
And you forgot Go crashes. If you think C++ error messages are long, you haven't seen a nil-pointer dereference in Go.
So please don't say Go doesn't have exception handling. It has code that detects exceptional conditions and attempts to handle them by unrolling the stack. Of course that's entirely different from "exceptions", right ?
Plus it once again points out the schizophrenic nature of Go. Go uses error codes. Except when it doesn't. Go doesn't have generics. Except when it does. Go doesn't allow polymorphism. Except in ~5 cases. No language on the planet allows for return type polymorphism (meaning of the function changes depending on what you assign it's result to) ... except of course Go.
This was one of the main, and valid, objections people had to languages like modula-2, oberon and the like in the past. And maybe I'm getting old, but my money's on that the pendulum will swing back again.
Furthermore I disagree with your assessment. Go and C insist on the same thing : in-line errors. In C that lead to errors being ignored in the vast majority of cases by "average" programmers. In Go ... well, here's the top-5 of Go projects. Go and take a look for yourself :
https://github.com/trending?l=go (note: changes every day. Now check if errors are ignored today or not. Today's answer 4 of the top 5 projects ignore almost all errors)
But I don't see how your comment addresses the complete unnatural way of thinking that C and Go promote. Handing errors in-line is not natural. This was one of the big complaints when we moved from C to Java/Python, a move supported by a lot of programmers.
So let's be honest here. Go's philosophy in practice is to make users ignore errors.
I really think (and it looks like we strongly disagree on this point) it's one of Go's virtues to really distinguish between what's part of the problem you're trying to solve and what can be considered as defaults from the computer (as a whole, i.e. both software and hardware). You shouldn't have to deal with these very different problems the same way.
As for your comment about the practice being to ignore errors in Go, I have to believe your words I guess (maybe I should have a look at these horrors you're talking about). This is clearly not the way I work but that's a serious fault from developers if they do. And, of course, that will lead to very serious bugs since an ignored error code is much more damageful than an uncaught exception. Sure, empty try-catch blocks are found in java too, but they tend to disappear.
1) I recover from such errors in Java all the time. It's easy : execute finally's ("defers"), execute retry policy (go, incidentally, makes generic retry policies very fucking hard due to lack of generics. It is impossible to write a method "TryThreeTimes(method, timeout, params)" in Go.
2) It is extremely disappointing that after all the effort that Go forces on you for error handling there are still a dozen classes of errors (of the kind you find often) that can kill your program, even with error handling perfectly up to the Golang author's standards.
3) These errors happen often enough that it's not reasonable to kill the program if they happen, so you absolutely need to recover in daemons and the like. So any reasonably large Go program has to consider exception handling, recovery, and alternate method returns.
4) You also have the C++ problem : libraries should never, ever raise an exception ("panic"). But of course, they do (ok granted, the Go standard library is reasonably good about this, but the same cannot be said for github stuff)
5) Nobody actually does the error handling. You never see anything other than "return err" or "exit program with log message" in error branches anyway)
> I really think (and it looks like we strongly disagree on this point) it's one of Go's virtues to really distinguish between what's part of the problem you're trying to solve
Yeah we disagree here. I feel Go's error handling standard prevents me from focusing on the problem independently of what might go wrong, and this limits my abstract thinking. What makes this even more frustrating is that this is exactly the same problem C had.
> This is clearly not the way I work but that's a serious fault from developers if they do.
With the guarantee of quality developers, I'd develop nearly anything in C++. There is no problem in C++ if you have a really good team and there's just no beating the C++ language in too many departments (portability, abstraction, compiler quality, tooling, power, control of complexity in huge programs ...).
But the great failing of C++ is simple : it doesn't support junior programmers, or otherwise somewhat incompetent teams very well at all.
The question here is : does Go ? I don't know, but to me it's not looking good. I would argue that the damage that can be done by slightly incompetent programmers in Go far exceeds the damage that can be done in most other languages (ok, granted, maybe it's better than C++, but definitely worse than Java).
for i := 0; i < 3; i++ {
res, err = f(arg)
if err == nil {
break
}
}
Writing this kind of behavior with try-catches is rather annoying, the try-catches being leaking all over the code.About C++, well, I almost agree ; this is my favorite programming language (in the sense it would be the one I'd use if I had to pick only one), but I think it is not the right tool for a lot of tasks (including the ones Go is targeting : servers and sysadmin tools). Java might be better for servers than Go, but for sysadmin tools it clearly sucks (because of startup time, mainly).
What I want is OCaml without people complaining about concurrency (which I suspect is mostly FUD - how many people who choose go "for performance" actually profile their programs? I get the sense there is this group of people who think any high-level language is inherently as slow as Python). Or Haskell without laziness (maybe Idris will eventually become this?), and perhaps with a lower-friction approach to I/O. Or Scala with native compilation (again I think complaints about this are probably mostly FUD, but if nothing else the JVM startup latency is real). Or any of these three getting the hype that Go gets, tbh.
Is it all just about the hype? Write code, ship products.
But yeah, I'm writing and shipping in Scala, and life is good, I shouldn't care about people hyping what I don't like.
I could and probably will blog about problems with the Haskell as well - nothing is perfect.
Map String Any
Is that a function call? A type declaration? What's a parameter to what? The go style map[string]interface{}
is, much as I love to hate on all things go, much clearer.It is just a regular type parametrized over two other types. And kinda that is the point - being expressive enough to describe a map type without explicit support.
Haskell doesn't seem to have that; any piece of Haskell code tends to look like a smooth surface of words separated by spaces. Function? Words separated by spaces. Type? Words separated by spaces. Instance? Words separated by spaces. There are no visual handles to latch onto, nothing to help you realise how this stream of words separated by spaces forms a tree structure (AST).
Haskell uses syntax and context to distinguish identifiers as follows:
- variables, lower case initial letter
- constructors, upper case initial letter
- type variables, lower case initial letter
- type constructor, upper case initial letter
and similarly for classes and modules. And of course, white space is function application.
So you know if it is a variable or a constructor by looking at the first letter. And you know if it is a value-level variable or a type-level variable (or constructor) by context (types can only appear in certain places, e.g. on the right hand side of `::`).
But, yes, they all look like words. They're not ambiguous though. :)
In java/go/c#, there are lots of statement and small expressions. In haskell expressions tends to be longer and are broken down in multiple local definitions (with let or where).
You learn to recognize important words (fmap, forM_, $, etc). You also learn to recognize how those words are associated, and how data is passed around.
Granted, there are people who abuse point-free notation to build complex and hard to read expressions. However I find that a codebase with rich informative types and a reasonable coding rule set is very pleasant to read.
In the go version [] is used for both indexing and type definition.
If its following a "::" then its part of a type signature and its a type constructor application. Otherwise, its part of an expression and its a regular function call.
BTW, Haskell capitalization is significant in Haskell. Identifiers starting with lower case are always regular functions. Types and type constructors always start with upper case.
> What's a parameter to what?
Application in Haskell is left associative so its
((Map String) Any)
Haskell is a functional language and programs are filled with function applications so using spaces for function application avoids a lot or syntactic noise. The only time I find this really bites me is that, due to automatic currying, passing the wrong number of arguments to a function can lead to hairy type errors. This is not a big deal if you use add a healthy amount of type annotations to your code though. Application in Haskell is left associative so its
((Map String) Any)
You seem to have stumbled on the exact conflict. Anybody who's seen the theory of lambda calculus will recognize the significance of what you've written, and how it applies to partial binding of functions.Anybody who hasn't will either be thinking of lisp or just be totally confused.
This is where the conflict is. Many developers actually know some programming theory and find Go woefully insufficient for anybody calling himself a programmer. And the ones that just fiddle around with things and make barely working programs without really knowing what they're doing are very happy that they don't have to know this in Go.
What's really going on is that people with real programming background find that Go forces them to think at the lowest possible level of abstraction, with no way up. And people without any programming background say "that's GREAT !".
> What's a parameter to what?
Answer:
> Application in Haskell is left associative so its
> ((Map String) Any)
Which looks crystal clear to me. This is also the part of the answer that user waps later quoted.
But if you give too few or too many arguments, you usually do get an error like: "Perhaps `foo` is applied to too [many|few] arguments?"
For example, I recently wrote a compiler in Ocaml for a scripting language. The type I used for the syntax tree has lots of type parametsrs:
type ('id, 'str, 'enum) stmtf =
(* big ADT definition goes here... *)
The 'id type parameter is the type of identifiers (names), 'str is for strings (they get interpolated) and 'enum is for constants.These type parameters give me some flexibility. When the parser builds the first syntax tree everything is still a string:
type syntaxtree = (string, string, string) stmtf
and after a pass to bind names the strings get converted to more informative types: type boundtree = (nameId, interpolation, string) stmtf
Up to now, we could get this flexibility by being untyped and defining all these fields in stmtf as "void pointer" or "interface". But the generics lets us make everything type safe. Central to this is the "functor map" over stmtf: val map_sstmtf:
id: ('i1 -> 'i2) ->
str: ('s1 -> 's2) ->
enum: ('e1 -> 'e2) ->
('i1, 's1, 'e1) sstmtf ->
('i2, 's2, 'e2) sstmtf
It takes 3 functions telling what to do to the id, str and enum fields, respectively and converts the stmtf by applying those fucntions to each field. The neat thing is that in the implementation of map_stmtf if we forget to apply apply one of these callbacks to one of the fields the code would not typeckeck. The code also won't typecheck if we apply a callback to the wrong field - say apply the "id" callback to a "str" field. This lets us catch at compile time bugs that wouldn't even have been caught at runtime because in the initial syntaxtree both "id" and "str" fields are represented by "string" and are thus prone to being confused with one another. Both of these turned out to be pretty useful as I evolved my language and changed the structure of my syntax tree.Liberal use of type parameters also gave many other benefits. For example it was trivial to modify the tree to add a line number next to all identifiers, all I had to do was update the type definition and then see the compilation errors to find the parts of my code that had to be updated.
type syntaxtree = ((position * string), string, string) stmtf
Finally, one extremely neat trick is that you can use type parameters in some places where you would have used recursive types. This pattern is described pretty well in this post I'm linking to and its the sort of thing that you can't really do without generics.A) I'm using a working language already. B) Read the comments on the mailing list regarding third party proposals/implementations from the Go authors.
In short: Complete waste of time.
I see what the author is coming from but generics would break what makes go great.
There has been times when I wanted generics, but it has generally meant that i was looking at the wrong side of the problem.
What. Have you even used a language with generics? Yes it can, very easily; you can do it in a traditional-OO way by having a sortable interface, or if you want to be really cool you do it with a typeclass.
There's already a language like that - Haskell.
Yes, this can sometimes help you to find silly errors in your code at compile time (and thus occasionally save you a bit of time) but, unfortunately, that occasional benefit doesn't make up for the productivity loss incurred from constantly trying to 'please the compiler'.
Dynamically typed languages offer fluidity in your logic where it is needed. For example, if you add an integer and a decimal (floating point) number together, there is nothing wrong about this mathematically, but a static compiler will complain! It's a limitation of the compiler not the programmer!
Statically typed languages force you to implement rigid class/type schemas within your logic (sometimes with complex hierarchies) - These schemas have to be significantly reworked whenever your business requirements change.
For instance, in Haskell I can type 2 + 3.75 and I get 5.75 right back -- no errors. That's because the type signature of + is "Num a => a -> a -> a," meaning I can add two of any type of Number (Integers and Floats are both Numbers, of course).
prog.go:13: invalid operation: x + y (mismatched types int and float32) [process exited with non-zero status]
I'd have to cast one of the variables to get it to work. Casting is unnatural - Maybe it's not so bad in this case, but in many cases it isn't desirable.