From Java to Go, and Back Again (2016)
opencredo.com
opencredo.com
"Casual" Java programmers usually miss the most important feature, what really makes it possible to do almost anything in Java. Reflection and runtime code generation. Every time I look at Go this ends up being a problem, worse than lack of generics. A lot of Java libraries use these features under the hood to make things like JPA and JSON serialization work.
Nothing I'm aware of comes close to Java in this, where you can redefine the language and rewrite compiled code at runtime while still maintaining execution and memory safety. Java agents, annotation processors, and libraries like JavaAssist, ByteBuddy, and ASM make seemingly impossible things pretty easy to write.
This is why annotations are used for everything in Java. They're often just tags used to find targets for rewriting code at runtime.
I was trying to write a thin ORM for Go and quickly found out it was impossible to do what I needed without blowing a bunch of nasty generated source files into the build. In Java, I can give the developer a nice clean interface and just generate the code when the app starts. Go forces you to expose all this to the user of your library
On the contrary java has been around since 1994. Who knows how mature golang and python will get after aging and maturing like java.
I'm not sure why people harp on the JVM so much. JavaScript uses a runtime with even more magic that uses 5-10x as much RAM. I think the crappy JVM's of the past along with bad memories of Java Applets tarnishes its reputation unfairly
Java 10 is slightly better but still slower than Java 8 by default. Enabling app CDS gets you back to comparable performance to Java 8. With AOT enabled (in addition to app CDS) it might be faster, but that just crashes on machine (and only works on Linux anyway).
So don't get your hopes up too much.
People are starting to experiment with AOT compilation via graal to make headroom in this space, but the techonology being used feels young, nascent, and experimental or immature:
https://medium.com/graalvm/instant-netty-startup-using-graal...
https://www.innoq.com/en/blog/native-clojure-and-graalvm/
Interestingly most high-level runtimes also have slow startup, including python, ruby, and node.js, but java's feels worse because people seem to want to use heavyweight frameworks like spring or aspectj that absolutely murder startup time, that for instance generate bytecode on startup or use classpath scanning to discover dependencies.
Newer frameworks like Vert.X and Dropwizard address this pretty well. We have a fairly large Dropwizard project that still starts in ~5 seconds. Also some written in Spring MVC that take 2 minutes...
Also, God AspectJ let's you create horrible abominations. And it feels like it was designed to encourage you to do so. A great example of why you shouldn't always use self modifying code even if it's easy to do.
Not only do you get instant startup, you also get a statically linked binary that doesn't need a JRE present to run. Throw it a JAR - get a native binary for your system.
Almost all third party commercial JDKs have some form of AOT support.
C#. No Type Erasure. More powerful reflection with less code. Not to mention the unbelievable power of Roslyn and being able to programmatically generate code with little effort.
If you love Java reflection, your mind will be blown by C#'s.
The huge problem is that it's not that much better than Java which has ~50X more open source software written for it. The day MS makes a bytecode transpiler so I can use my Java libraries in C# I'll never touch Java again. The languages are so similar I have no idea why they haven't done it yet
I always see the .net fanboys tout how much better C# is than Java with generics, await/async, etc. but at the end of the day it's not that much better when you can achieve the same thing in Java with better open source support and across oses.
I know there's .net core now but I am not going near it until it matures.
LINQ -> streams, QueryDSL, RxJava, JOOQ etc
Async/await -> Comsat/Quasar
The type system is lightyears better in C# but it was rare that I needed those features. Usually you can hack around the limitations and end up with something (slightly cruftier) that does the same thing in Java. Mockito, Retrofit, and RxJava are good examples of hacking about the type system until you have something good enough. That said, there are definitely times I still miss C#'s stellar type system. Easily the best of any language I've touched besides maybe Typescript (which is designed by the same guy)
Yadda yadda, if I wanted to "fix" the lack of an open source community in C#, I'd start with the Unity3D community.
Python as a scripting language makes sense, in this arena, anyways, as it's the scripting language for Blender. A single scripting language for 3D software and game engine in a game studio just makes sense.
I can go for a coffee when Blender does some kind of data transformation, I can't do that in a middle of a rendering a frame 60 times per second.
Meanwhile, the interesting technologies, especially relating to web development, ops/deployment, or data storage and processing are first-class citizens in Linux (and macOS gets a free ride, because POSIX and desktop). Most of them don't use Java , but enough do: Lucene/ElasticSearch, Cassandra (and probably DynamoDB, even if it isn't OSS), Hadoop, Neo4j, Kafka, and more.
It's not even close. Please understand I'm not hating on C#, or saying Java was why these things are successful (definitely not), but I think it helps to understand the eco-systems surrounding both C# and Java. C# may be a nice language, and may be able to run on Linux. In 2005 (C# 2.0) it was almost revolutionary, but now it's just another language. Meanwhile everybody else has been learning and using the other languages to build great things - distinctly without Microsoft.
I agree with all you said, but i'm referring to the previous comment where ms has been all but asleep in the last 10 years :D Anyway talking about language and dev-environment are two completly different conversations, and i'm not wrong saying that Java as language stagnated far too long. I never said the .net eco system thrived meanwhile :D
the languages are so similar because microsoft started with J++ and got a lawsuit from Sun (https://en.wikipedia.org/wiki/Visual_J%2B%2B).
https://www.codeproject.com/articles/1440/introduction-to-vi...
https://www.microsoft.com/en-us/download/details.aspx?id=471...
> It's funny when Java users complain about type erasure, which is the only thing Java got right, while ignoring all the things it got wrong.
I do bare metal/low level embedded, kernel drivers, and occasionally shared libraries. Java is useless for practically all of my typical use cases.
You can also make shared libraries with Java these days. Look at SubstrateVM. It turns Java code into a .so file that doesn't depend on the JVM, compiled ahead of time, with a C API exported.
> Look at SubstrateVM. It turns Java code into a .so file that doesn't depend on the JVM, compiled ahead of time, with a C API exported.
Thanks, will do. I've been mostly eyeing Webassembly for some "cross-platform shared object" use cases.
https://www.ptc.com/en/products/developer-tools/perc
https://developer.android.com/things/
Of course it isn't suited when trying to fit something into PIC with 64KB.
Java's real issues like in two aspects: 1) Bad Java programmers have built a culture of writing "overpatterned" enterprise code (with FactoryBuilderFactorFunctions) 2) Horrible GC/iffy performance for a modern language
#1 is reason enough to avoid Java like the plague. #2 makes it totally unsuitable for heavy numerical computation or systems level applications. Nothing like huge GC spikes to liven up your day.
2) Without a doubt native shines here. I’ve also been a victim of those massive GC spikes in the past. But I think saying the JVM has horrible GC seems a bit hyperbolic as it’s quite matured and more than adequate for most usecases.
Speaking as someone moving from Ruby to Go, I actually like the strong stance that Go takes against magic. The original article acknowledges that Go works the way that it does by design, and acknowledges the reason very fairly: the language is firmly on the side of making code easy to read, even at the expense of making it less elegant to write.
When I have to do Java, I keep recalling Brazil[0].
Sure, if you look at JEE stuff, you'll run into it...
Rust and Nim provide the sort of thing I mean, hygienic macros - they're nicer than reflection because you keep type safety and you keep run-time performance without generating a bunch of transient source files. Nim's also got a nice RTTI interface, too.
>Almost all the tricks I’ve built up over the years for creating embedded DSLs in Java or Ruby or Clojure are simply unavailable.
>...if there’s one complaint I’ve heard consistently from my fellow developers over the years, it’s that they can’t understand my code until they also understand the set of abstractions I’ve introduced
"Tricks" are lovely for personal projects, but no one wants to trawl through them at 3am when the server has gone down. Go removes a lot of tricks, and that makes people sad, but it makes total sense for a language design for large teams. You can still use Go for one-man projects, of course (I do), but don't expect it to be as whimsical an experience as using Ruby or Lisp.
If it happens in any regularity, then tricky abstractions are not the main problem. Apparent lack of QA and overall quality is.
But my point is, this should not play like this with any kind of regularity. At all. And it is not happening regularly for mission critical projects. It is not that we write without bugs either, it is more that you have processes and multiple systems to avoid middle of the night quick fixes into production.
Yeah, but the point was made to support an argument (that code should have fewer abstractions so that it's faster to grasp any part while reading in a hurry, as opposed to more abstractions so that it's shorter and more generic, even if that means having to study the big picture to get what some smaller part of code does).
Now, if devs routinely had to quickly understand some local code at 3am to fix bugs, as per the example, then sure, fewer abstractions and more clear local code would be better.
But since your example is contrived, where does that leave the main argument?
Java is a wonderful language with a terrible culture. Choosing Go or even Kotlin over Java is liberating because it allows an engineer to escape the cultural baggage that comes with Java.
What kinds of baggage? The type of baggage that blindly demands using magical frameworks like Spring and JPA or worse engages in best practices chants as they implement 45 interfaces and create thousands of excess classes because SOLID and DDD said to do so. Yes, I'm exaggerating some but this is a real problem on Java teams.
I sincerely hope that a culture adjustment is included in the Java 11 release.
That sounds like ~2005 Java to me, where the ~whole ecosystem was that way, and consequently a lot of Java devs left for Rails.
However, I think in Java's today (since ~2010) there are definitely two (or more camps) of old-school enterprise Java (which is like you say) but also lightweight start-up Java (which is what I'm more familiar with, and matches your "Java can be a great language when used well" comment).
So, anyway, IMO, it's not the entire Java culture anymore, although of course I believe you that that is the one you're currently in.
And if I didn't want the magic, projects like Dropwizard and Vert.x are there. But I'd rather have the most productive out of the box experience possible.
It was already like that with C, C++, Smalltalk, Delphi, VB before Java came around.
Rest assured there will be GoEE and GoSpring if enterprise architects ever put their hands on Go for the same kind of scenarios.
Maybe is true that I can understand what is happening in every single line of code in my screen, what I found really hard is to understand how those line interact with the rest of the code base.
I honestly believe that there are some part of the code base that should manage the low level stuff like managing files, writing to buffers or interact with the file system and other part that should compose/manage/move those low level parts.
Using go and its focus on "simplicity" it feels like these basic abstraction fall apart and that in every part of the code base I can find something doing quite low level stuff.
I find go quite good for small, self contained projects but honestly I believe that big projects like docker could benefit more from a more structured approach.
Am I the only one feeling this pain?
At this point I have to wonder if in 10 years the Golangers will actually find themselves in a good place. I don't see these codebases aging well at all.
Returning to Java (and Kotlin) development after a couple of months of Go development, [...] I discovered the value of a certain sort of "plain speaking" in code.
This resonates with me. I've spent 1 year+ in each of C, C++, C#, PHP, Java, Python, and JS. There are obvious benefits to taking advantage of a platform's strengths, but the "plain" code has merits too. Easier to reason about, often more maintainable, faster to ramp up new team members, etc.
Simpler code almost always wins. Predicting the future is hard, refactoring simple code that does the minimum it needs to is comparatively easy. Don't write what-if code, YAGNI.
Furthermore, when opening a new project you can just read through the interfaces quickly and get a good idea of what the code is doing. You can't just read thru a whole bunch of implementations and get as good of an idea.
If the implementation doesn't provide more methods than the interface (a transparent class in GoF terminology) you should not name it.
In Java, the implementation is usually created inside a static method (a static factory) of the interface as a lambda or an anonymous class.
Again, moot point if there's only one implementation.
> If the implementation doesn't provide more methods than the interface (a transparent class in GoF terminology) you should not name it.
> In Java, the implementation is usually created inside a static method (a static factory) of the interface as a lambda or an anonymous class.
And how's that not needlessly convoluted and ugly? You're illustrating guitarbill's point about applying design patterns blindly.
I do acknowledge that XImpl is kinda ugly as name.
Until you cast the object...
You should use truly immutable data types unless you specifically need otherwise, most modern languages and libraries encourage you to do so.
Now, you can be good at planning ahead and know what the abstraction should be in many cases. This is still much rarer than all cases. Indiscriminately creating an interface for every class means you're probably not giving the interface much thought.
I strongly agree with this point.
I speculate that some people tend to add unnecessary code because they are thinking of all those well-written libraries they use every day, which have evolved to support multiple uses, including the uses that clearly they don't need but they can see are useful to others. Therefore they conclude good software must add features that are useful for other coders!
I strongly agree ! At my current company we are ramping up the team and want to go from 4 very senior engineers to 35. The very complex architecture based on functional programming that we are using will probably be a major hindrance here.
I don't understand your remark on interfaces though. I tend to have many, even for one click method. If I want to have a callback for a list item tap, I create an interface for that very specific item, that way it is very easy to decouple view and business and it also is extremely easy to debug in Intellij. In one click I get from the interface declaration in the viez to it's implemenentation in the business logic.
Why does it hinder debugging in your use case ?
Examples of things that C# has over Java:
- Operator overloading: .Add() is really not that much more annoying to write...
- Properties: Shorthand for getters and setters, because auto-completion isn't a feature in IDEs since forever. And, like Java, they've introduced lambdas, but have then also changed the automated code generation of Properties in their IDEs to use lambdas, because apparently even an IDE needs to be lazy and give junior devs a harder time.
- The out-keyword: Somewhat formalized way of passing along a variable to have it populated via side effects by a method. In Java, you'd refactor your code to not need two return values, or you'd return an object with those values in class variables (or you'd just do this dirty side effect method without telling anyone).
[0] Bit like Python: https://docs.microsoft.com/en-us/dotnet/api/system.tuple?vie...
> ...if there’s one complaint I’ve heard consistently from > my fellow developers over the years, it’s that they can’t > understand my code until they also understand the set of > abstractions I’ve introduced"
Now I mostly use Scala in an FP style I find I reuse abstractions. The abstraction I want almost always exists in a library. Monads, applicatives, monoids, etc. actually work for code reuse, and this makes it much easier to read code. When I look at a code base such as http4s (https://http4s.org) I find I can read it without much difficulty even though I'm not familiar with the code.
So my contention is you can reduce expressivity to make code easier to read, or you can increase expressivity so that truly reusable abstractions can be created. (You basically need higher-kinded types in addition to the usual FP abstractions.)
With Go it feels like all you need is a dependency manager and maybe a Makefile for more complex builds.
Go tools are not intended to be a full build system, so for more complex builds, Makefiles are still needed.
There's an alternative to using Apache Groovy for writing Gradle config scripts: https://blog.gradle.org/kotlin-meets-gradle . If you also use Kotlin instead of Java for your software on the JVM then you can stick to one language for everything.
This is the YAGNI principle in XP - “you ain’t gonna need it”: https://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it
It builds an xml dependency tree of your classes. That's not hard to understand.
Everything it does is well documented on their site as well.
Is @component and @restcontroller and @requestmapping really that hard to understand?
If it doesn’t work how you expect / want it to work, now what? With a library I can go the declaration and see what’s going on and what I might be able to do. With annotation magic I have no choice but to go to google where my choices will be diving into extremely verbose and not particularly well written documentation or cargo culting based on stack overflow answers.
So I wonder, having a smaller vocabulary, it might make learning the language easier. And maybe your first pass on a new piece of code is quicker to read. But after that first pass, or once you've learned the expanded vocabulary, wouldn't it now be even easier to read? And not using it would just make things redundant and appear like noise.
It's like reading: "He felt a deep attachment that was unique and stronger then any other to her." And then you learn about "love". Now from that point on, I'd rather read "He loved her." Seem just easier to read once you know the concept.
I see what the Author is getting at, but he's missing the primary mechanism Go uses for this: Interfaces. This is hard to explain in the abstract, so here's an example I gave in a talk once: (bit.ly/1mBisa6)
Suppose we are building a new podcast platform which dynamically splices advertisements into mp3s. We will do this with an HTTP server. In Go you do:
f, err := os.Open("somefile")
...
http.ServeContent(w, r, "", time.Now(), f)
ServeContent takes an io.ReadSeeker. Now we build a Splice function: func Splice(
src io.ReadSeeker,
splice map[time.Duration]io.ReadSeeker,
) (io.ReadSeeker, error)
Used like this: f1, _ := os.Open("/tmp/1.mp3")
f2, _ := os.Open("/tmp/2.mp3")
f3, _ := os.Open("/tmp/3.mp3")
final, _ := mp3.Splice(f1, map[time.Duration]io.ReadSeeker{
time.Second * 5: f2,
time.Second * 10: f3,
})
Splice uses two additional functions. First the MultiReadSeeker, which simply concatenates ReadSeekers: func NewMultiReadSeeker(
readSeekers ...io.ReadSeeker,
) io.ReadSeeker
Next the SectionReadSeeker, which gives you a section of a ReadSeeker: func NewSectionReadSeeker(
src io.ReadSeeker,
offset, length int64,
) io.ReadSeeker
So it ends up being: final := NewMultiReadSeeker(
NewSectionReadSeeker(f1, 0, A),
f2,
NewSectionReadSeeker(f1, A, B-A),
f3,
NewSectionReadSeeker(f1, B, C-B),
)
So it's pretty straightforward. There are some really powerful ramifications from these abstractions:1. The file is never built in its totality in memory. Rather we are representing the operations in a lazy fashion that only get executed when you use them.
2. The ReadSeeker supports seeking, which means we can resume from the browser, or seek to the end and skip the middle bits. With the go HTTP library we basically get this for free.
3. Lots of things use these interfaces. For example if I want to write this to disk, I can just io.Copy it. Or suppose I want to use this as another source for a second splicing. It's ReadSeekers all the way down, so I can do that with no changes. With some tinkering I'm pretty sure you could hook this up to S3 too.
That's not true. Here's their example written to be more generic:
func mapThingToThing(inSlice interface{}, mapperFn interface{}) []interface{} {
mapper := reflect.ValueOf(mapperFn)
input := reflect.ValueOf(inSlice)
output := make([]interface{}, input.Len())
for i := 0; i < input.Len(); i++ {
item := input.Index(i)
output[i] = mapper.Call([]reflect.Value{item})[0]
}
return output
}
and calling: fn := func(item string) string {
return item + "!"
}
res := mapThingToThing([]string{"a", "b", "c"}, fn)
for _, item := range res {
fmt.Printf("item = %+v\n", item)
}
I didn't say it was pretty or good, but it can be done. Really confused by the downvotes.It's not type safe. Any other modern/static language would use generics to ensure inSlice and mapperFn both operate on the same types.
People downvoting you probably see this as obvious for programmers that want/choose a static language like Go/Java/TypeScript/etc.
Yes, I get that, but that's also moving the goalpost. The post claimed that "you can’t write a generic map from an array of T to an array of R", but you can (if you take generic to mean "general" and not "specifically using the mechanism of generics").
I think the author just didn't know that this was possible, or his concern would have been about the lack of type safety with the reflection method.
no it is not. The whole point of generic programming is compile time type safety for incomplete types.
> but you can (if you take generic to mean "general" and not "specifically using the mechanism of generics").
not in a compile time type safe way. Again, you can't negate the whole point of generics, that's a cop-out. You just wrote Python like dynamic code in Go here, with reflection (AKA runtime behavior) which incures a severe performance penalty.
And let's not get started on the fact that you're returning []interface{} which is often pretty useless when the type of the element matters. You introduced some serious code smell with that function since you threw away type safety.
If you really want to write a generic mapper the only serious way to do that in go is to use reflect.MakeFunc then a function variable of the choosen signature. But again reflection is so slow it's not even worth to begin with.
Except that's not what the post's gripe was about. His gripe was that you need to write a new function for every type you want to map, because there's no way to write one that is generic for all types. He doesn't mention type safety at all, so yes, the solution I posted meets the goal of his complaint.
>And let's not get started on the fact that you're returning []interface{} which is often pretty useless when the type of the element matters. You introduced some serious code smell with that function since you threw away type safety.
Did you miss the point where I said "it's not good or pretty" ? Or do you just want to bitch about everything? Your whole post reads like someone who has never written a container type in Go. Guess how you do it? You use interface{} and cast in the caller!
I'm not bitching about anything (you don't need to use this kind of language here, it is not reddit), I'm telling you that piece of code you wrote is pretty useless.
It is not compile time type safe and it is slow due to the use of reflection. This is not an alternative to generics or generic functions in the language. It fixes nothing.
When it comes to Go, you don't know what you're talking about. Learn to write a container type and then get back to me. Here's a Heap https://golang.org/src/container/heap/heap.go?s=2105:2138#L5... See the return type? interface{}. This is how it's done in Go. How did you think it was done without generics? IntHeap, StringHeap, FloatHeap, etc?
"You can't do X because Go doesn't have Y" was the original point. My response was "you do X with Z." Whether or not Z is good or bad is completely orthogonal to the original point seeing as how it's idiomatic in Go.
No, it's not orthogonal. The part you cited even mentions strong typing, thereby IMO it is pretty obvious type-safety is a requirement here. Otherwise what would even be the point of the exercise? Mapping of some data to some other data? You can do that in _any_ turing-complete language.
The comment said "you can’t write a generic map from an array of T to an array of R" and, indeed, you can't do that in Go. Your code maps a slice of dynamic values to a slice of dynamic values. It doesn't map array of T to array of R, as requested, because there's simply no way to express that in Go.
Besides, I think think it even actually _is_ idiomatic since you used reflection.
https://appliedgo.net/generics/
See point 4 and 5. It is idiomatic.
It is how container types are done in Go.
You can't define your own compile type safe container types in Go at first place. You demonstrated nothing with your piece of code, you just wrote some dynamic stuff that doesn't care about types, this isn't a container type, you ignore the type of the element.
I've demonstrated how you map a function over a list containing any type. How would you do it? Provide a solution.
you've demonstrated you are unable to do it while conserving the type of an element.
You iterate over an array with a range loop which allows the code to stay compile time safe. That's the only thing one can call idiomatic go code here. You threw out compile time safety for nothing trying to prove some point that makes no sense to begin with.
val exclaimed = strings.map { it + "!" }
Should be
val exclaimed = plain.map { it + "!" }
I have mentioned this before but the reason I like Java so much is the mature and cohesive ecosystem. Every single problem has been solved in Java and all the solutions look similar so you can readily pick them up. I don’t find that to be the case in other languages.