Hacker News new | past | comments | ask | show | jobs | submit login

I am reading and writing Go and Java almost daily. Java has a tendency to be written in an over-engineered way. The Go community has an inclination towards cautious abstractions.

Take interfaces. In Java you might start with them. In Go - in the best case - they emerge, when it's time for them. Java has more mature tooling, but then, I cannot remember gathering runtime insights with Java quicker than with Go pprof[1].

Java is rock solid and more and more libraries are written in lightweight and clean way. Go has more momentum and also a growing number of curious users, who explore the language in creative ways[2].

[1] Google image search for "go pprof": https://goo.gl/6l7t1b

[2] Just discovered this amazing guy recently, https://github.com/fogleman/ln, https://github.com/fogleman/nes




"Take interfaces. In Java you might start with them. In Go - in the best case - they emerge, when it's time for them."

And it's a relatively subtle language feature that does this, the way that any struct that implements a given interface automatically conforms to that interface without having to be declared. Which means you can declare an interface that foreign packages already conform to, and then freely use them. Which means that you can code freely with concrete structs to start with, and then trivially drop in interfaces to your code later, without having to go modify any other packages, which you may not even own.

I completely agree that on paper Java and Go look virtually identical. But the compounding effect of all those little differences makes them substantially different to work with. Good Go code does not look like good Java code. You'd never confuse them.


> Which means you can declare an interface that foreign packages already conform to, and then freely use them.

But you cannot do the reverse: you cannot make a type from a foreign package conform to your interface by adding new methods to it. This is because, with structural typing, it's not safe to add methods to types in other packages, since if packages B and C both were to add a conflicting method Foo to a type from package A, B and C could not be linked together. This is a major downside of structural typing for interfaces. Swift, for example, has the "extension" feature, and Java's design allows for it in principle, but it's fundamentally incompatible with Go's design.


The list of languages that permit that in a principled way is short, and the list of languages where it's a good idea is even shorter. I've come to the conclusion that it's generally not a good idea. (And it's not Go making me say that, it's more Haskell.) Keeping dependencies flowing in one direction seems to be a good plan.


I totally disagree. It's not only a good idea, it's essential for certain core functionality to work at all. Take serialization, for example: without this feature it's impossible to write a good serializer as a library without code generation (messy and brittle) or reflection (very slow). To give another example, try writing a linear algebra library that's generic over the data type without this feature (and such libraries are essential for high performance graphics programming). The only way I can think of to make it work is to make callers explictly box their numbers into wrapper types you create, which is really ugly and causes various other problems.

I don't understand the idea that not having this feature somehow helps enforce dependency order either. Extension implementations of traits don't give you any more abstraction-breaking power than downcasting interface{} to a concrete type not defined in your package does. In fact, they're pretty much strictly less powerful.


> To give another example, try writing a linear algebra library that's generic over the data type without this feature (and such libraries are essential for high performance graphics programming).

I disagree. My best, fastest to compile code has always focused on just one type. You mention graphics programming: for 99.999% of the cases you want to pick a type. Usually, this will be float, and thus for SIMD a 4 or 8-vector of these, like https://github.com/aktau/threedee-simd/blob/master/include/t.... Despite the support of recent graphics cards for doubles, the fact is that they are usually quite a bit slower. Even when precision bound, there are usually tricks that will let one keep using floats (esp. wrt to Z-buffers).


It's very useful to use different types (for example, fixed point) in VBOs in order to save space and/or reduce CPU side processing.


Are we talking about the same thing? That is the most full-throated defense of orphan instances I've ever heard. The reaction to such things is generally... less positive... to put it lightly.


Orphan instances arise when the implementation of the interface is in neither the same package as the package that defines the type nor the package that defines the interface. I'm defending the latter (which Go's rules rule out), not orphan instances.


> But you cannot do the reverse: you cannot make a type from a foreign package conform to your interface by adding new methods to it.

I thought you could.

You don't add the methods directly to it, but you can easily embed the foreign type into a new type that confirms to the interface you want.

  type FooWrapper struct {
    Foo
  }

  func (fw FooWrapper) SomeFunc() {
  ...
  }


That's making a new type, which causes a lot of friction. For example, a []Foo array is not castable to a []FooWrapper array without recreating the entire thing.


There is a big downside to structural typing as golang implements it though. Refactoring tools. They quite simply cannot do the same kinds of safe refactoring something like a Java refactoring tool can do, because you can't be sure if the function you are trying to rename, add parameter too, etc. is actually the same function in question.

There are times when I love the structural typing aspects of golang (trivial dependency inversion) and there are times when I hate it (nontrivial renames), its one of many trade-offs you have to be prepared for in golang.


I believe you can do renames with the oracle tool


> Which means you can declare an interface that foreign packages already conform to, and then freely use them.

Except that it is very difficult to find out which struct implements what interface, you'd have to go look through the code. Not to mention there is always the possibility of accidentally implementing an interface. This has already caused real issues it seems (there was a blog post about it).

There already exists a solution for this issue, via typeclasses.

But golang doesn't have generics.




Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: