Hacker News new | ask | show | jobs
by codygman 3596 days ago
As someone who writes Go every day for work, I can't agree that Go is simple. Using a language for analytics without generics can be quite painful and error prone.

Go is a language that pushes remembering corner cases and failure conditions onto the programmer rather than the language and runtime itself.

When you already have to remember a myriad of corner cases for business logic, also remembering so many corner cases for your code hurts productivity.

I also believe that languages exist to make getting to an end result in given domains easier. Go does not make my life easier.

I really hope it gets generics. I wish it would do away with nil/null.

Nim is a very good language that actually accomplishes the simplicity Go wanted imo.

Go affords simplicity to the Go compiler writers at the cost of burdening Go users with having to remember inane things.

8 comments

> I wish it would do away with nil/null.

I don't think that's possible to retrofit onto a language. Best you can do is to add non-nullable types. But zero values are so core to Go's semantics that I kind of doubt it's possible to even add those in a sensible way.

> Go is a language that pushes remembering corner cases and failure conditions onto the programmer

Can you elaborate on this? I write go for 3+ years and I have no idea what corner cases and failure conditions do you mean.

Examples that bother me sometimes. YMMV of course.

Writing to a closed channel panics, but writing to a nil channel blocks forever.

Appending to a nil slice works fine, but inserting into a nil map panics.

If you have a function that returns an error struct, and you wrap it with another function that returns the error interface, nil returns from the inner function will no longer test equal to nil.

Defining a method with a receiver type of Foo, rather than <star>Foo, means all modifications to the Foo get silently dropped. This can also happen to methods that correctly take a pointer receiver, if their caller incorrectly takes a value receiver.

Maps are not threadsafe/goroutine-safe.

Expression evaluation order is not defined, and varies between compilers. (https://github.com/golang/go/issues/15905)

What you describe is all true, but honestly, this is such a small number of corner cases compared to traditional languages(C, C++, python, javascript, etc.), that it is really not a big deal. Also most of this is clearly documented.
Agreed about C/C++/JS, though I'm curious to hear your thoughts about Python. There are only two really common gotchas in Python that I tend to notice:

- Using an iterator more than once silently produces nothing. I notice this when I insert print statements to debug something, but then accidentally turn the following for-loop into a no-op. The Python 3 change that made more top-level functions return iterators made this problem more common, though I agree with the performance justification for doing it. It would be nice if iterating again after hitting the end raised an Exception, though I'm sure that would break all sorts of code that assumes it doesn't.

- Mutating function default arguments affects all subsequent calls. This is most common Python gotcha people seem to talk about.

A corner case about Go which makes me absolutely crazy: calling Reset() on a timer which has already fired has the biggest gap between "What I expect to happen" and "What actually happens" of any stdlib I've ever worked with.

https://gist.github.com/patio11/bc883d566778c323742432c203e6...

(You can see it here in the playground, but try it on your local machine if you don't believe me and/or think the playground has an inconsistent understanding of what time actually means: https://play.golang.org/p/ltdV9dI609 )

You never drained the longTimer channel, so when you say "We agree that longTimer has fired, right?"; that's not quite true. After you call Reset(), you're still getting the value from the first firing, because that's the first time you read from the channel at all.

The docs are quite clear on this behavior and say "Timer will send the current time on its channel after at least duration d." -- key words being at least and says nothing about when you choose to read from the channel.

It's even worse. The longTimer has fired, and it sent a message on the channel just as it was supposed to. When Reset() is called, it causes a second firing and a second message. Here is the code, corrected to illustrate. The output times are exactly as one would expect.

https://play.golang.org/p/lntgH6tkiF

This particular issue is unfortunate - it can't be changed without potentially breaking existing programs.

See https://github.com/golang/go/issues/11513 and https://groups.google.com/forum/#!topic/golang-dev/c9UUfASVP... for some background.

Thanks for sharing! I read up on it. This whole "channel draining" concept seems powerful.
All standard libraries have their quirks, corner cases and peculiarities. This is not specific to Go. Or are you saying it is worse on Go?
I presume an example would be something like remembering to check `rows.Err()` in `package sql` at the end of iterating through all rows. If you check the error each iteration while calling `rows.Scan()` but forget to check the `rows.Err()` at the end, it could potentially be much, much later that you find out something went wrong.
Methods in the standard library panicking if the method receiver is nil for one.
Having tried to use Go for analytics, I agree that it's not a good fit for that use case. (It does work well for scripts and simple servers, though.)
Looking at things people write in Go, I can't shake the feeling Go is a new /bin/sh with xinetd built in.
The way I personally use it ... I'd say you're exactly right. And adding that to an easy cross compilation/platform story that can side-step C toolchains in many cases, garbage collection, and intelligible concurrency, and you've got an extremely useful tool.
So the main reasons for Go are the fact 'coproc' keyword added to Bash since version 4 is still considered experimental[1] and the fact that a vast majority of modern developers don't know shell good enough.

[1] https://www.reddit.com/r/bash/comments/4ksl7w/golanglike_gor...

  As someone who writes Go every day for work, 
  I can't agree that Go is simple.
You may be confusing "simple" with "good". A simple solution to a complex problem may not be a good one. Go can be simple and still not a great solution because it pushes complexity to a higher level.
I think there's a difference between "simple" and "simplistic", and people don't often get it.
People rarely differentiate "complicated" and "complex". I get the feeling from GO that it was designed for making complex programs.
Go handles simple problems well but copy pasting more "simplicity" as complexity scales results in a complex program.
I'd say Go defines "simplicity" as "simple to write (good enough to run for non-critical parts)"
> Nim is a very good language that actually accomplishes the simplicity Go wanted imo.

Does it have "cheap threads", like offered by Go?

Kind of. Nim offers a thread pool for CPU intensive tasks[1] and async await for IO intensive tasks. Currently, the two don't really mix but work is ongoing to change that.

1 - http://nim-lang.org/docs/manual.html#parallel-spawn

> Nim is a very good language that actually accomplishes the simplicity Go wanted imo.

Coincidentally, I just looked at Nim this evening, wrote some code and came away with the opposite impression. I'll copy-paste my sort-of-blogpost on this from [1]:

Nim itself feels a lot unlike Go and, interestingly, a lot like C++:

1. Non-orthogonal features. In C++, there are references, which are like pointers, but not quite, so you always have to think about which one to use. In Nim, non-orthogonal features include tuples vs. objects, and sequences vs. openarrays.

2. Feature creep. Just look at the language manual [2]: generic functions, type classes, 10 calling conventions (10!), garbage collection (not sure if optional or not), inline assembler, operator overloading, exceptions, an effect system, AST macros. Name any contemporary programming language feature, chances are that Nim has it.

Then there's the documentation. The language manual [2] is okay-ish, considering the size of the language. But when I dive into the library reference, the built-in module "system" [3] is so outlandishly huge and the documentation so badly formatted that it takes a lot of hunting to find what's in this module (and what isn't). Just look at all the duplicate entries in the navigation bar on the left. The system module should definitely have been split into multiple modules for the multiple concerns it covers, possibly with reexports in the actual "system" module to make sure they're all imported by default. Some parts should just be moved out of it, for example the whole file IO business belongs into "os" IMO.

Another thing that concerns me about the standard library is how many duplication is going on in there. There are at least 4 different XML parser AFAICS, and two regular expression libraries, both based on the same backend (pcre). This might be the same confusion that most languages suffer from in their pre-1.0 stabilization phase, but especially then, it's a strong argument not to use the language in production pre-1.0.

The thing that really killed it for me that I was able to produce SIGSEGV by accident, without the compiler warning me about it. I think I wrote something along the lines of:

  type Config = tuple
    someSetting:    string
    anotherSetting: string
  
  proc readConfigFile(path: string): Config =
    var file = open(path)
    defer: file.close()
    # TODO: implement the rest
    var cfg: Config
    return cfg
  
  var cfg = readConfigFile("./example-config")
  echo(cfg.someSetting) # this produces a SIGSEGV; probably because
                        # the memory backing `cfg` is not initialized
In 2016, I expect any new language to warn me about (or outright refuse to compile) code that might access uninitialized memory or do any other unsafe stuff, especially for a program that will run as root.

[1] https://github.com/holocm/holo/issues/8#issuecomment-2412822... [2] http://nim-lang.org/docs/manual.html [3] http://nim-lang.org/docs/system.html

Your example doesn't produce SIGSEGV for me. It prints out the text, "nil".

However, if I write

    echo(cfg.someSetting[0])
it will segfault. This makes sense because it dereferences a null pointer (in Nim-speak, a nil value). I would guess that's what you experienced.

Dereferencing a null pointer is not unsafe. The program cleanly exits. Compiling that via C is sketchy though, because the C compiler may treat provable null pointer dereferences as undefined behavior.

I would tend to agree, based on instinct, that Go is poor for data science work (as it happens I am about to find out for real in the coming weeks :| ). To build systems to feed data into some other analysis platform: definitely, but dynamic number crunching, not so much. Rob Pike probably knows this better than most; Sawzall would probably not exist otherwise.
I was just reading an article describing Sawzall needing to sandbox code really makes me think of Haskell, or some other language with an effect system.
And this : http://www.unofficialgoogledatascience.com/2015/12/replacing... replacing Sawzall with Golang :|
> Nim is a very good language…

Is it as good at concurrent services as golang is?

There are many use cases for which Nim might very well be a very good language other than concurrency.
Yes, but since we're talking about golang, it's the info I'm looking for here.