continuation of notes-computer-programming-programmingLanguageDesign-prosAndCons-golang
http://monoc.mo.funpic.de/go-rant/
http://go-lang.cat-v.org/quotes
---
http://www.stanford.edu/class/ee380/Abstracts/100428-pike-stanford.pdf
--
http://www.informit.com/articles/article.aspx?p=1623555
---
craigyk 4 days ago
| link |
I went and looked up some of my notes back when I was trying out Go. I hope to learn from others wether these are real issues or simply my misunderstandings.
1. So we don't get generics, but the language built-ins seem to get to be type-parameterized (channels, slices, maps). Unfortunately the syntax for doing so for each of these is inconsistent (probably as a result of being special-cased rather than dog-fooded using language-level generics): []float, map[float]float, chan float.
2. Built-in types seem to receive other special treatments as well, which includes special initialization keywords (make vs. new) and built-in functions (len, cap, etc.) but I don't see why this needed to be the case, even for performance reasons. There's no reason why the these built-in types couldn't pretend to implement built-in interfaces to make more transparent with user-types while having the compiler optimize them with special-case functions for efficiency.
3. Unused variables are a hard error which is a completely understandable stance. Unfortunately, I think people may use workarounds to get around this. Also, I can't believe unused variables are a hard error, but uninitialized variables are not! Instead we are supposed to trust that everything is OK since they get initialized to some kind of "zero" value that isn't even under the developer's control.
4. Other small quibbles: I think pattern matching on function arguments could have been implemented as sugar that uses interfaces and method calls under the covers. Also named return values are ugly, and the function declaration syntax could have been made more concise.
reply
---
" ... A good collections library should have the choices required to get asymptotically optimal algorithms built with almost no fuss, with the choices between ordered/unordered and set/list/map and queue/stack/heap being easy to find. Java undeniably does this really well; even if Go still does this ok.
Sometimes when using Go channels, I think I might be better off just going back to Erlang due to its pattern matching. The most troublesome part of verifying the correctness of Erlang code is following the implicit protocol in the weakly typed structs being passed around. There is stronger typing in Go, but without normal subtype declarations, it becomes convoluted to do declare a whole bunch of structs with some referential integrity (ie: a FooReply? struct that contains a ref to a FooRequest?, let alone an IPPacket struct that contains a TCPPacket
| UDPPacket, ... either a safe union or just normal subtyped references, etc.) |
Thanks, Rob, for your comment. Some of the subtype requirements can be handled well in Go using interfaces. In my program too, I had some useful interfaces. "
---
" 2014-01-08 Another go at Go ... failed!
After a considerable gap, a gave Go another go! The Problem
As part of a consulting engagement, I accepted a project to develop some statistical inference models in the area of drug (medicine) repositioning. Input data comprises three sets of associations: (i) between drugs and adverse effects, (ii) between drugs and diseases, and (iii) between drugs and targets (proteins). Using drugs as hub elements, associations are inferred between the other three kinds of elements, pair-wise.
The actual statistics computed vary from simple measures such as sensitivity (e.g. how sensitive is a given drug to a set of query targets?) and Matthews Correlation Coefficient, to construction of rather complex confusion matrices and generalised profile vectors for drugs, diseases, etc. Accordingly, the computational intensity varies considerably across parts of the models.
For the size of the test subset of input data, the in-memory graph of direct and transitive associations currently has about 15,000 vertices and over 1,4000,000 edges. This is expected to grow by two orders of magnitude (or more) when the full data set is used for input. Programming Language
I had some temptation initially to prototype the first model (or two) in a language like Ruby. Giving the volume of data its due weight though, I decided to use Ruby for ad hoc validation of parts of the computations, with coding proper happening in a faster, compiled language. I have been using Java for most of my work (both open source as well as for clients). However, considering the fact that statistics instances are write-only, I hoped that Go could help me make the computations parallel easily[1].
My choice of Go caused some discomfort on the part of my client's programmers, since they have to maintain the code down the road. No serious objections were raised nevertheless. So, I went ahead and developed the first three models in Go. Practical Issues With Go
The Internet is abuzz with success stories involving Go; there isn't an additional perspective that I can add! The following are factors, in no particular order, that inhibited my productivity as I worked on the project. No Set in the Language
Through (almost) every hour of this project, I found myself needing an efficient implementation of a set data structure. Go does not have a built-in set; it has arrays, slices and maps (hash tables). And, Go lacks generics. Consequently, whichever generic data structure is not provided by the compiler can not be implemented in a library. I ended up using maps as sets. Everyone who does that realises the pain involved, sooner than later. Maps provide uniqueness of keys, but I needed sets for their set-like properties: being able to do minus, union, intersection, etc. I had to code those in-line every time. I have seen several people argue vehemently (even arrogantly) in golang-nuts that it costs just a few lines each time, and that it makes the code clearer. Nothing could be further from truth. In-lining those operations has only reduced readability and obscured my intent. I had to consciously train my eyes to recognise those blocks to mean union, intersection, etc. They also were very inconvenient when trying different sequences of computations for better efficiency, since a quick glance never sufficed!
Also, I found the performance of Go maps wanting. Profiling showed that get operations were consuming a good percentage of the total running time. Of course, several of those get operations are actually to check for the presence of a key. No BitSet? in the Standard Library
Since the performance of maps was dragging the computations back, I investigated the possibility of changing the algorithms to work with bit sets. However, there is no BitSet? or BitArray? in Go's standard library. I found two packages in the community: one on code.google.com and the other on github.com. I selected the former both because it performed better and provided a convenient iteration through only the bits set to true. Mind you, the data is mostly sparse, and hence both these were desirable characteristics.
Incidentally, both the bit set packages have varying performance. I could not determine the sources of those variations, since I could not easily construct test data to reproduce them on a small scale. A well-tested, high performance bit set in the standard library would have helped greatly. Generics, or Their Absence
The general attitude in Go community towards generics seems to have degenerated into one consisting of a mix of disgust and condescension, unfortunately. Well-made cases that illustrate problems best served by generics, are being dismissed with such impudence and temerity as to cause repulsion. That Russ Cox' original formulation of the now-famous tri-lemma is incomplete at best has not sunk in despite four years of discussions. Enough said!
In my particular case, I have six sets of computations that differ in:
types of input data elements held in the containers, and upon which the computations are performed (a unique combination of three types for each pair, to be precise),
user-specified values for various algorithmic parameters for a given combination of element types,
minor computational steps and
types (and instances) of containers into which the results aggregate.These differences meant that I could not write common template code that could be used to generate six versions using extra-language tools (as inconvenient as that already is). The amount of boiler-plate needed externally to handle the differences very quickly became both too much and too confusing. Eventually, I resorted to six fully-specialised versions each of data holders, algorithms and results containers, just for manageability of the code.
This had an undesirable side effect, though: now, each change to any of the core containers or computations had to be manually propagated to all the corresponding remaining versions. It soon led to a disinclination on my part to quickly iterate through alternative model formulations, since the overhead of trying new formulations was non-trivial. Poor Performance
This was simply unexpected! With fully-specialised versions of graph nodes, edges, computations and results containers, I was expecting very good performance. Initially, it was not very good. In single-threaded mode, a complete run of three models on the test set of data took about 9 minutes 25 seconds. I re-examined various computations. I eliminated redundant checks in some paths, combined two passes into one at the expense of more memory, pre-identified query sets so that the full sets need not be iterated over, etc. At the end of all that, in single-threaded mode, a complete run of three models on the test set of data took about 2 minutes 40 seconds. For a while, I thought that I had squeezed it to the maximum extent. And so thought my client, too! More on that later. Enhancement Requests
At that point, my client requested for three enhancements, two of which affected all the six + six versions of the models. I ploughed through the first change and propagated it through the other eleven specialised versions. I had a full taste of what was to come, though, when I was hit with the realisation that I was yet working on Phase 1 of the project, which had seven proposed phases in all! Back to Java!
I took a break of one full day, and did a hard review of the code (and my situation, of course). I quickly identified three major areas where generics and (inheritance-based) polymorphism would have presented a much more pleasant solution. I had already spent 11 weeks on the project, the bulk of that going into developing and evaluating the statistical models. With the models now ready, I estimated that a re-write in Java would cost me about 10 working days. I decided to take the plunge.
The full re-write in Java took 8 working days. The ease with which I could model the generic data containers and results containers was quite expected. Java's BitSet? class was of tremendous help. I had some trepidation about the algorithmic parts. However, they turned out to be easier than I anticipated! I made the computations themselves parts of formally-typed abstract classes, with the concrete parts such as substitution of actual types, the user-specified parameters and minor variations implemented by the subclasses. Conceptually, it was clear and clean: the base computations were easy to follow in the abstract classes. The overrides were clearly marked so, and were quite pointed.
Naturally, I expected a reduction in the size of the code base; I was not sure by how much, though. The actual reduction was by about 40%. This was nice, since it came with the benefit of more manageable code.
The most unexpected outcome concerned performance: a complete run of the three models on the test set of data now took about 30 seconds! My first suspicion was that something went so wrong as to cause a premature (but legal) exit somewhere. However, the output matched what was produced by the Go version (thanks Ruby), so that could not have been true. I re-ran the program several times, since it sounded too good to be true. Each time, the run completed in about 30 seconds.
I was left scratching my head. My puzzlement continued for a while, before I noticed something: the CPU utilisation reported by /usr/bin/time was around 370-380%! I was now totally stumped. conky showed that all processor cores was indeed being used. How could that be? The program was very much single-threaded.
After some thought and Googling, I saw a few factors that potentially enabled a utilisation of multiple cores.
All the input data classes were final.
All the results classes were final, with all of their members being final too.
All algorithm subclasses were final.
All data containers (masters), the multi-mode graph itself, and all results containers had only insert and look-up operations performed on them. None had a delete operation.Effectively, almost all of the code involved only final classes. And, all operations were append-only. The compiler may have noticed those; the run-time must have noticed those. I still do not know what is going on inside the JRE as the program runs, but I am truly amazed by its capabilities! Needless to say, I am quite happy with the outcome, too! Conclusions
If your problem domain involves patterns that benefit from type parameterisation or[2] polymorphism that is easily achievable through inheritance, Go is a poor choice.
If you find your Go code evolving into having few interfaces but many higher-order functions (or methods) that resort to frequent type assertions, Go is a poor choice.
Go runtime can learn a trick or two from JRE 7 as regards performance.These may seem obvious to more informed people; but to me, it was some enlightenment!
[1] I tried Haskell and Elixir as candidates, but nested data holders with multiply circular references appear to be problematic to deal with in functional languages. Immutable data presents interesting challenges when it comes to cyclic graphs! The solutions suggested by the respective communities involved considerable boiler-plate. More importantly, the resulting code lost direct correspondence with the problem's structural elements. Eventually, I abandoned that approach.↩ "
--- http://oneofmanyworlds.blogspot.com/2014/01/another-go-at-go-failed.html
--
"
http://golang.org/pkg/container/
http://docs.oracle.com/javase/tutorial/collections/interfaces/index.html
http://docs.oracle.com/javase/tutorial/collections/implementations/index.html "
"
pron 6 hours ago
| link |
... and those don't even include Java's long list of concurrent collections.
reply
jwn 6 hours ago
| link |
Or the other Collection implementations offered by https://code.google.com/p/guava-libraries/.
reply
jbooth 6 hours ago
| link |
Or the ability to write collections in the first place, as enabled by generics. You can't even write a collection in Go unless you're dealing with interface{} or unsafe.Pointer and casting a lot.
I really like the language, enough to use it and work around the lack of generics, but that's a glaring weakness. They need some way to enable container classes.
reply "
--
" pcwalton 2 hours ago
| link |
Generics, inheritance, and the factory pattern are completely orthogonal features. Adding generics would not entail adding inheritance, mandating the factory pattern, or any other slippery slope feature. ... Generics have nothing to do with inheritance and the factory pattern. There are lots of languages that have generics but neither inheritance neither factories: SML, OCaml, Haskell, etc.
Inheritance makes generics harder, in fact, because type inference becomes undecidable in the general case.
Factories are basically a workaround for functions not being able to be freestanding (unattached to a class) in Java (the "kingdom of nouns"), a problem that Go doesn't have.
reply"
--
"
pjmlp 5 hours ago
| link |
... and the concurrent libraries of futures, fork-join, tasks
reply
sixthloginorso 5 hours ago
| link |
Java's concurrency primitives and libraries are really overlooked in these discussions. Is the syntax too off-putting, or is it merely that Java is unfashionable?
reply
RyanZAG? 4 hours ago
| link |
Very much unfashionable for the HN crowd. Most of the new research in concurrency is happening in Java and funded by the high performance trading industry - an industry which is very far from the HN crowd. The new Java8 stampedlock is a good example. It's possible to implement it in C++ as well, but because of the guarantees required by the lock it is a very difficult lock to integrate into C++ code. On the other hand, the JRE guarantees the correct constraints for Java code making a stampedlock very easy to use [1]. The performance of a stampedlock also seems to be the best case for any multi-reader environment. [2]
[1] http://concurrencyfreaks.blogspot.com/2013/11/stampedlocktry...
[2] http://mechanical-sympathy.blogspot.ca/2013/08/lock-based-vs...
reply
scott_s 29 minutes ago
| link |
I find your claim that most new research in concurrency happens in Java strange. Perhaps you are unfamiliar with academic research in concurrency and parallelism? A way to get a small taste is to look at recent papers from the conference Practice and Principles of Parallel Programming (PPoPP?).
reply
jamra 3 hours ago
| link |
I don't think there is anything wrong with Java as a language, but I have a question for you. Does Java's concurrency engine allow for many threads like Go or is it similar to C# in how your threads are not lightweight and are therefore limited to ixN where N is the number of CPU cores and i is a small integer < 10.
reply
pcwalton 2 hours ago
| link |
You can't spawn 80 1:1 pthreads on an 8-core machine? Huh?
Operating systems have been able to handle an order of magnitude more pthreads than that since the day pthreads were introduced.
reply
pron 3 hours ago
| link |
Java doesn't have a "concurrency engine", but a very large set of concurrency primitives: schedulers, locking and lock-free data structures, atomics etc.
To answer your question, yes: my own library, Quasar[1], provides lightweight threads for Java.
[1]: https://github.com/puniverse/quasar
reply
pjmlp 2 hours ago
| link |
The Java language specification does not define how threads are implemented.
The first set of JVMs did implement green threads, which are what goroutines are. Shortly thereafter most of them switched to red threads, aka real threads.
You can still find a few JVMs that use green threads, like Squawk.
https://java.net/projects/squawk/pages/SquawkDesign
Other than that, java.util.concurrent provides mechanisms to map multiple tasks to single OS threads.
reply "
--
"
cosn 51 minutes ago
| link |
I wrote most of those already, if anyone needs them, help yourself. Haven't had the need for a bit set, but I guess I can add it to the TODO list :)
https://github.com/cosn/collections/
That being said, I completely agree that the author chose the wrong language for the problem at hand.
reply
redbad 5 hours ago
| link |
> Go's zoo of builtin data structures is really, really
> poor compared to Java.This is a poor comparison because idiomatic Go typically doesn't use the `container` package.
reply "
---
" Either stick with what you know or use a tool that has very specific optimizations for the problem you're going to tackle (and no, concurrency is not a good option to jump off the JVM as it has incredible concurrency support already: see https://github.com/LMAX-Exchange/disruptor for the fastest concurrent bus I've seen in any language). "
--
"
_pmf_ 4 hours ago
| link |
> see https://github.com/LMAX-Exchange/disruptor for the fastest concurrent bus I've seen in any language
The genious behind LMAX is the way they bend Java's object layout features; they achieve nice performance in spite of using Java, not because of it. Some decade old message passing libraries (OpenMP? et al.) will probably outperform LMAX without even trying.
reply
twic 1 hour ago
| link |
Actually, i doubt it. The machine code that ends up running at the heart of a disruptor is pretty minimal, and it executes precisely zero lock or atomic operations when handing an object from one side to the other. I am not aware of any other message-passing system that lightweight. I would be genuinely interested to hear about one.
reply "
--
"
barrkel 7 hours ago
| link |
It's not very like Java at all. The only OO-like feature it has is interfaces, which are more like Haskell's type classes combined with existential types than anything else. (The resemblance here is actually very close, implementation-wise: roughly, vtables at the per-instance level rather than per-class level).
The other semi-interesting things it has are (a) channels + parallel functions and (b) the return of error codes for error handling.
Java things it doesn't have start with reflection, bytecode (CPU independence), dynamic runtime code generation, inheritance hierarchies, generics, "everything is an object", array covariance, exceptions as an idiomatic error propagation scheme, scoping based on nested namespaces, no code outside of classes, nested types with scoping to match, inner classes with implicit outer class self pointer, only value parameter semantics, and only reference user-defined types.
In fact most of what makes Java distinct from the subset it shares with C is missing, except for a garbage collector.
reply "
--
" NateDad? 7 hours ago
| link |
Wow, go is totally not Java-lite. Trying to program in it like it is will just cause headaches. The only conceivable way that I can think of that it could be called that is because it is compiled and has a garbage collector. Otherwise... they are very different languages.
Go is what you'd get if python and C had a bastard love child that turned out different from either, but still retained some of the beauty of each of its parents.
reply
paulnechifor 4 hours ago
| link |
And Nimrod would be their legitimate favorite son.
reply "
--
"
jerf 5 hours ago
| link |
Probably more accurately Python--; it cuts out a lot of features from Python, a great deal of which you weren't using,
...
(By "not using", I mean for instance that in Python, like Javascript, at any moment, you may set a new method on a particular instance, and the Python runtime must deal with that, such as by spending a lot of time looking up references that never actually change in a real program. This is a really expensive feature that you are paying for all the time, yet rarely using. A great deal of the JIT speedup you can get in such languages is working out how to skip those lookups optimistically until someone actually changes them.)
"
"
fauigerzigerk 4 hours ago
| link |
I miss Python's named parameters with default values. They are surprisingly hard to replicate in Go. Also list comprehensions.
reply
NateDad? 3 hours ago
| link |
Named parameters can be sort of gotten by passing a struct as the parameter, then you can do
type fooParams struct { Name string Age int Address string }
foo( fooParams{ Name = "Bob", Age = 24 } )
Defaults are a lot harder to do in a way that doesn't just suck. You can make a DefaultFooParam? that has all the defaults... but it's not pretty.
List comprehensions never seemed like a big deal to me. It's 3-4 lines for a loop, which is probably easier to read than the list comprehension anyway.
reply
baq 1 hour ago
| link |
> List comprehensions never seemed like a big deal to me. It's 3-4 lines for a loop, which is probably easier to read than the list comprehension anyway.
that's totally a nope.
def qsort(L):
if len(L) <= 1: return L
return qsort([lt for lt in L[1:] if lt < L[0]]) + [L[0]] + qsort([ge for ge in L[1:] if ge >= L[0]])"
--
http://oneofmanyworlds.blogspot.com/2011/11/draft-2-of-proposal-for-generics-in-go.html
--
"
tldr: I will be using a lot of generics and do tons of set operations. I will chose Go, a language that doesn't have generics and sets.
(I won't post the Go code for criticism, and I couldn't stand golang-nuts)
reply
shadowmint 7 hours ago
| link |
and; go isn't the best language to write everything in.
I think that's a fair conclusion for him to have drawn. He certainly wasn't hating on go.
(golang-nuts is openly hostile to any kind of criticism, so much so that there's been talk of a community code of conduct; which the community didn't want. I don't blame him.)
reply
"
--
"
maxk42 1 hour ago
| link |
I thought it was a pain in the ass until I realized golang really does have a sort of dynamic parameter:
func test(myVar interface{})will accept any type at all. You can then use Go's type switch or a call to reflect.TypeOf?() to invoke different behavior based on the parameter's type. To my way of thinking, it's pretty simple.
reply "
--
" Goranek 8 hours ago
| link |
Seems that 2014 won't be a good year for Go on HN. Same old story as with Node and Mongo. First they ignore you, then they praise you, then they hate you.
reply
--
Nogwater 1 day ago
| link |
Not knowing enough about Erlang/OTP or Go: Would it be possible for more of OTP to be replicated in a Go 2.0? What's missing? What would it take? What can never be replicated?
reply
jerf 1 day ago
| link |
You can recover a substantial portion, but there's a few things you can't quite get back. Since Go is a mutable-state language, and also a shared-state language (isolation is by convention, not by language design), you have to live with the consequences of that. In my supervisor tree implementation, the restart of a monitored goroutine is just to fire off a new goroutine on the exact same object again; if you still have bad state lying around and immediately crash again, well, too bad, the supervisor can't do anything about that. (Except not thrash the processor with endless restarts.) In contrast, when an Erlang supervisor restarts a process, it is guaranteed to be a fresh process with no shared state from the one that just crashed. It's much more likely to not immediately crash.
--
waffle_ss 1 day ago
| link |
Besides the issues that jerf mentioned, Erlang also has per-process garbage collection built right into the VM. Last time I checked Go's garbage collection was still stop-the-world, mark-and-sweep, making it less suitable for soft real-time systems than Erlang.
reply
--
pron 13 hours ago
| link |
Well, I like Go, and it certainly has its use cases. But I personally worked on a hard-realtime safety critical mission Java project (if the program gave the wrong answer people could die, and if it gave the right answer late, tens of millions of dollars would be lost). We couldn't have done that without Java's concurrent data-structures (which Go doesn't have), without Real-Time Java's hard realtime guarantees, and it would have been very cumbersome without Java's brand of dynamic linking.
So sometimes Go is good enough, but sometimes you need the full power of the JVM.
reply
--
" Thoughts on Go
As the adage says, programmer time is much more valuable than processor time. So you should use Go.
Go is a high-level language. It is easy to write in just like Python.
It has a rich standard library just like Python.
Go’s unit testing framework is phenomenal.
Unlike Python, Go is statically typed, which makes refactoring large codebases unbelievably easier.
Like Python, Go supports a form of duck-typing, through interfaces. Interfaces are the bees knees.
Like Python, Go is garbage collected.
Unlike Python, Go frees you from worrying about distinctions between synchronous and asynchronous calling conventions or threadpool switching.
Like Twisted, Go is evented for I/O, but unlike Twisted, Go stack traces are much more manageable and Go’s profiling tools provide much greater clarity about execution order.
Unlike Python, Go is fast, compiled, and leaves the runtime execution to only the work that needs to be done.It’s a shame Go didn’t exist when Space Monkey started. We’re very lucky to have it now. " article on dumping Python for Go
--
" The concept of channels is nice, it lends itself easily to data pipelines and to SIMD parallel programming. It is not a breakthrough, though. SIMD is easily done in many many languages. Other than that, it's pretty down to earth. There's a myriad nice things (first class functions, closures, garbage collection, strong(ish) typing), but nothing ground breaking. There are also some choices that smell, namely the lack of inheritance due to the flat type hierarchy (and the obvious need for a fix), or the inability to overload methods and operators. " -- https://news.ycombinator.com/item?id=7803827
" To me, "no imposed GC" is a non-negotiable element of being a systems programming language. So I am far more drawn to Rust than Go. " -- https://news.ycombinator.com/item?id=7805145
" sergiosgc 51 minutes ago
| link |
http://golang.org/doc/faq#What_is_the_purpose_of_the_project
« No major systems language has emerged in over a decade, but over that time the computing landscape has changed tremendously.
(...)
We believe it's worth trying again with a new language, a concurrent, garbage-collected language with fast compilation. Regarding the points above [cut on edit]:
reply
0xdeadbeefbabe 12 minutes ago
| link |
Go has pretty neat semantics for channels and goroutines, but it presumes you want threads and blocking IO. Or in other words, "hey kid here's some rope"
Your approach to concurrency matters even more than the tool you use. Twisted (for python) has a good approach for example.
reply
"
--
jerf 17 hours ago
| link |
"People are leaving Python for Go because people have always left Python for fast compiled languages."
I think the angst about Go comes from the fact that someone who leaves Python for Java may still come back, because you can develop far more quickly in Python than Java. But someone who leaves Python for Go probably isn't coming back... my experience is that it is slightly slower (10-20%, YMMV but we're certainly not talking integer multiples) to cut out a prototype in Go, but that said prototype runs an order of magnitude faster, can potentially be optimized without much work for another order of magnitude (though ultimately this is more a reflection of Python's slowness than Go's speed), and is then much easier to maintain and extend, even on the timescale of a week or two, to say nothing of larger time scales.
A couple of people elsewhere in the comments here assume that Python must still be much easier to develop for than Go. It really isn't anymore; it turns out the combination of garbage collection and structural-typing-esque interfaces pretty much does anything you might have ever wanted Python to do, and a great deal of the rest of the differences turn out to be far less important in practice than in theory.
I first saw the idea in Joel Spolsky's "How Microsoft Lost the API War" [1], under the heading "Automatic Transmissions Win The Day", that the primary innovation of the 1990s was simply garbage collection instead of memory management. (As he is aware the idea is older than 1990, one presumes he means that it became practical and widespread.) The languages that spread this innovation, the "scripting languages" like Perl and Python and PHP and Javascript, changed a lot of things at once, which as any good scientist knows means it was hard to tell what about those changes actually contributed to the enhanced productivity that they certainly did bring. My experience with Go certainly gives me further belief in Joel's thesis... you read a bullet point listing of the Python features and Go features and it seems obvious that Python is a wildly better language than Go, yet... I've learned after all these years and all the languages I've tried out to ask myself, if the features are so important, why don't I miss them? Because in practice, I don't. Rip closures out of Go, and I'd miss that. Rip out the goroutines and I'd miss not having something like generators or something, indeed, the language would nearly be useless to me. But I certainly don't miss metaclasses or decorators or properties. I will cop to missing pervasive protocols for the built-in types, though; I wish I could get at the [] operator for maps, or implement a truly-transparent slice object. But that's about it.
[1]: http://www.joelonsoftware.com/articles/APIWar.html
reply
nostrademons 12 hours ago
| link |
My experience was Go was about 2-3x slower (development speed) than Python for prototyping. Web programming, though, which is a particular strength of Python and a particular weakness of Go. YMMV, of course.
I actually really do miss list comprehensions and properties and pervasive protocols for built-in types and really concise keyword/literal syntax. I don't miss metaclasses, and I only vaguely miss decorators. (Go has struct annotations and compiler-available-as-a-library, which in some ways are better.) Properties in particular are really useful for the evolvability of code; otherwise you have to overdesign up front to avoid a big rewrite as you switch from straight struct fields to accessors.
I'm actually leaning towards Java 8 + Jython 2.7 as I consider what language to write my startup/personal projects in. Jython 2.7 gives me basically all the language features I actually cared about in Python, and it fixes the Unicode mess that necessitated Python 3. It has no GIL and solid multithreading support. The Java layer gives you all the speed of Go and more. And the interface between them lets you seamlessly use Java within your Jython scripts, so you can easily push code down into the Java layer without having to rewrite your whole product.
reply
jerf 4 hours ago
| link |
Yes, the Go web prototyping story is a bit weak. If you want to do serious development in it I think it's pretty good, because frankly for years our web frameworks in most other languages have been unbelievably contorted around not having something like goroutines in ways that we've stopped even being able to see because they're so darned pervasive, but if you just want to slam a decent site out (and there's nothing wrong with that) there's no great story there right now.
reply
---
http://ridiculousfish.com/blog/posts/go_bloviations.html
---
in Golang, if you end a file without either a newline or semicolon to terminate the last line of code, you get this error:
" test.go:75:1: expected ';', found 'EOF' "
this dude found that confusing (presumably they wanted to error message to mention the lack of newline, not just the lack of semicolons)
--
many ppl don't like the Golang requirement that every variable be used:
http://ridiculousfish.com/blog/posts/go_bloviations.html#go_damnableuse
--
apparently, one good effect of the requirement that every variable be used is that it forces programs to check return values
--
go's C interop
"Go has a foreign function interface to C, but it receives only a cursory note on the home page. This is unfortunate, because the FFI works pretty darn well. You pass a C header to the "cgo" tool, and it generates Go code (types, functions, etc.) that reflects the C code (but only the code that's actually referenced). C constants get reflected into Go constants, and the generated Go functions are stubby and just call into the C functions.
The cgo tool failed to parse my system's ncurses headers, but it worked quite well for a different C library I tried, successfully exposing enums, variables, and functions. Impressive stuff.
Where it falls down is function pointers: it is difficult to use a C library that expects you to pass it a function pointer. I struggled with this for an entire afternoon before giving up. Ostsol got it to work through, by his own description, three levels of indirection. " -- http://ridiculousfish.com/blog/posts/go_bloviations.html#go_ccompatibility
--
"
Unicode
Go looooves UTF-8. It's thrilling that Go takes Unicode seriously at all in a language landscape where Unicode support ranges from tacked-on to entirely absent. Strings are all UTF-8 (unsurprisingly, given the identity of the designers). Source code files themselves are UTF-8. Moreover, the API exposes operations like type conversion in terms of large-granularity strings, as opposed to something like C or Haskell where case conversion is built atop a function that converts individual characters. Also, there is explicit support for 32 bit Unicode code points ("runes"), and converting between runes, UTF-8, and UTF16. There's a lot to like about the promise of the language with respect to Unicode.
But it's not all good. There is no case-insensitive compare (presumably, developers are expected to convert case and then compare, which is different).
Since this was written, Go added an EqualFold? function, which reports whether strings are equal under Unicode case-folding. This seems like a bizarre addition: Unicode-naïve developers looking for a case insensitive compare are unlikely to recognize EqualFold?, while Unicode-savvy developers may wonder which of the many folding algorithms you actually get. It is also unsuitable for folding tasks like a case-insensitive sort or hash table.
Furthermore, EqualFold? doesn't implement a full Unicode case insensitive compare. You can run the following code at golang.org; it ought to output true, but instead outputs false.
package main import "fmt" import "strings" func main() { fmt.Println(strings.EqualFold?("ss", "ß")) }
Bad Unicode support remains an issue in Go.
Operations like substring searching return indexes instead of ranges, which makes it difficult to handle canonically equivalent character sequences. Likewise, string comparison is based on literal byte comparisons: there is no obvious way to handle the precomposed "San José" as the same string as the decomposed "San José". These are distressing omissions.
To give a concrete example, do a case-insensitive search for "Berliner Weisse" on this page in a modern Unicode-savvy browser (sorry Firefox users), and it will correctly find the alternate spelling "Berliner Weiße", a string with a different number of characters. The Go strings package could not support this.
My enthusiasm for its Unicode support was further dampened when I exercised some of the operations it does support. For example, it doesn't properly handle the case conversions of Greek sigma (as in the name "Odysseus") or German eszett:
package main import ( "os" . "strings" ) func main() { os.Stdout.WriteString?(ToLower?("ὈΔΥΣΣΕΎΣ\n")) os.Stdout.WriteString?(ToUpper?("Weiße Elster\n")) }
This outputs "ὀδυσσεύσ" and "WEIßE? ELSTER", instead of the correct "ὀδυσσεύς" and "WEISSE ELSTER."
In fact, reading the source code it's clear that string case conversions are currently implemented in terms of individual character case conversion. For the same reason, title case is broken even for Roman characters: strings.ToTitle?("ridiculous fish") results in "RIDICULOUS FISH" instead of the correct "Ridiculous Fish." D'oh.
Go has addressed this by documenting this weirdo existing behavior and then adding a Title function that does proper title case mapping. So Title does title case mapping on a string, while ToTitle? does title case mapping on individual characters. Pretty confusing.
Unicode in Go might be summed up as good types underlying a bad API. This sounds like a reparable problem: start with a minimal incomplete string package, and fix it later. But we know from Python the confusion that results from that approach. It would be better to have a complete Unicode-savvy interface from the start, even if its implementation lags somewhat. " -- http://ridiculousfish.com/blog/posts/go_bloviations.html#go_unicode
--
complaints about the Go runtime/compiler's errors:
" If you index beyond the bounds of an array, the error is "index out of range." It does not report what the index is, or what the valid range is. If you dereference nil, the error is "invalid memory address or nil pointer dereference" (which is it, and why doesn't it know?) If your code has so much as a single unused variable or import, the compiler will not "continue operation," and instead refuse to compile it entirely. " -- http://ridiculousfish.com/blog/posts/go_bloviations.html#go_errors
--
" The Go compiler does not support incremental or parallel compilation (yet). Changing one file requires recompiling them all, one by one. You could theoretically componentize an app into separate packages. However it appears that packages cannot have circular dependencies, so packages are more like libraries than classes. " -- http://ridiculousfish.com/blog/posts/go_bloviations.html#go_compiletimes
--
complaint about golang polymorphism on return type, as applied to channel reads:
" Another syntax / semantics oddity is the behavior of reading from channels (like a pipe). Whether a read from a channel blocks depends on how the return value is used:
res := <- queue /* waits if the queue is empty */ res, ok := <- queue /* returns immediately if the queue is empty */
This bears repeating: the behavior of a channel read depends on how the return value is (will be) used. This seems like a violation of the laws of time and space! " -- http://ridiculousfish.com/blog/posts/go_bloviations.html#go_syntax
--
" You can create a goroutine with any function, even a closure. But be careful: a questionable design decision was to make closures capture variables by reference instead of by value. To use an example from Go's FAQ, this innocent looking code actually contains a serious race:
values := []string{"a", "b", "c"}
for _, v := range values {
go func() {
fmt.Println(v)
done <- true
}()
}
The for loop and goroutines share memory for the variable v, so the loop's modifications to the variable are seen within the closure. For a language that exhorts us to "do not communicate by sharing memory," it sure makes it easy to accidentally share memory! (This is one reason why the default behavior of Apple's blocks extension is to capture by value.) " -- http://ridiculousfish.com/blog/posts/go_bloviations.html#go_concurrency
--
https://news.ycombinator.com/item?id=7913918
--
DAddYE?