Hacker News new | ask | show | jobs
by noisy_boy 1864 days ago
I think we shouldn't point fingers at Java when it comes to Go's verbosity.

I can simply do dict.contains("foo") in Java vs having to go through a verbose hoop:

    if val, ok := dict["foo"]; ok {
        //do something here
    }
Common operations like filtering/transforming a collections are very concise in Java - I'm certain doing so in Go will take atleast 5x as many lines:

    collection.stream().filter(...).map(...).collect(...)
Don't get me started on having to go through iota hoops to declare enums.
3 comments

If I was trying to justify Go's verbosity in this case I think I would say "a property check on the dict incurs a cost, by making it somewhat more verbose you encourage people not to check more times than needed".

At least that's the kind of thing I've heard about other verbose things you need to do in Go.

There’s a lot of things wrong with this. First, this has nothing to do with the actual reason for the verbosity. The actual reason is that a value may or may not be present in a map, that’s a universal problem. Most languages just return nil or an Option value, but Go returns an error and forces you to handle it. No comment from me on whether that’s good or bad, but that’s the reason the code is like that.

The real problem is you’ve just made up a reason that the code is that way, and the reason is also crazy. No language purposefully makes something verbose simply to discourage doing it because of a performance concern. Even if they did, key lookups are constant-time complexity and are among the most efficient things you can do in all of programming. So that wouldn’t even make sense as something you’d want to discourage.

Err... Go does exactly what "most languages" do too! You can write:

    value, _ := map[key]
and if map[key] doesn't exist, value will be set to the "zero value" of the map's type. So Go doesn't force you to handle the error. The actual reason for the "verbosity" is just that you can't simply write

    if(map[key]) { ...
because map[key] returns two values (and "if" wants to have a boolean expression). But if you really have to do this so often that it's bothering you, you should question yourself why...
You can actually write:

  value := map[key]
and if map[key] doesn't exist, value will (still) be set to the "zero value" of the map's type.

The actual reason you cannot simply write:

  if(map[key]) { ...
aside from the extraneous parens, is because Go syntax does not permit implicit default values/truthiness in its 'if' statements like that (as you say "if wants to have a boolean expression"). That's the real reason for the verbosity here.

But you certainly can write:

  if map[key] != "" { ...
if the map contained strings, or:

  if map[key] { ...
if it contained booleans, etc. (For what that may be worth: checking for default value isn't the same as checking membership in all instances, of course).

Because map[key] can in fact return one — or two — values, depending on its usage context.

Go forces you to handle the error, which you agree with because you handled it by explicitly ignoring it via _.
I was just speculating, this is the kind of thing I heard as justification for other things in Go.

I guess in this case the real reason is trying to force you to handle the error, and that the error and value are returned separately.

Last time I wrote Java was before it had nice things so I may be slightly out of date in my assessment of its verbosity. I really wish it had decorator syntax like Kotlin does:

    class Foo(val delegate: Collection) : Collection by delegate
    {
        override fun length() = delegate.length() + 42
    }

    val foo = Foo(delegate: LinkedList())
    foo.add(1)
    foo.add(2)
    for e in foo {
        print(e)
    }
    foo.removeAll()
    foo.length() // = 42
I think a lot of the verbosity I remember was due to the sheer size of the type of interfaces you'd encounter in practice and an inability to easily compose them without implementing your own set of forwarding decorators (io stream style). That and getters/setters or rather the lack of any language-level support for dynamic properties.
Do you really want to inherit all the functions of Collection? Isn't this the anti-pattern everyone complains about on inheritance vs composition? This just looks like it's confused about whether it is a collection or has a collection.

Personally, I'd just rather take a collection by composition and only expose the small part of the API I actually want.

I guess though this is the standard decorator pattern you'd normally see from GoF.

It also breaks LSP when you “extend but reduce”. Not saying there aren't uses for “have-a”. More that writing a decorator in Java is awful.
I'm a C++ developer who has been writing more and more go code over the last year, and go's iota enum (alongside generics - having to write a search method for every different slice type gets grating after a while) is my biggest gripe with go. They really aren't any better than just global constants