Meanwhile the samber/ro example is incomplete (Subscribe(...)?), and the source includes some weird stuff: https://github.com/samber/ro/blob/22b84c8296c01c4085e8913944...
Not to mention heaps of reflection and panics: https://github.com/samber/ro/blob/22b84c8296c01c4085e8913944...
The functionality and expressiveness might be fantastic, but I would evaluate it very carefully before use.
You can see this your self if you edit the markup in your browser’s inspector and add `contenteditable` attribute to the surrounding <pre>, then navigate down a line... it will jump forward just by slightly less then a column per indent level.
Libraries that enable terse seemingly magical things tend to just hide a lot and make code harder to read. You end up having to become an expert in what amounts to a DSL on top of the language.
Edit: I've maintained a full codebase with R2DBC and I can assure you many developers came scratching their heads to me sometimes on tell me why are we doing this again when we can only have finite connections to DB and those connections can be 1-1 mapped to worker threads.
Speaking of the code examples, I am not convinced at all. The supposedly bad example is idiomatic and understandable to me, and I have a much better idea of what's going on than in the supposedly good examples. It contains the kind of constructions I expect to see when working on a Go codebase, and it will be easier to modify correctly for another Go developer coming in later. Please, work with the language instead of forcing it to wear a programming paradigm it doesn't like.
Such as:
> Together, these tools make Go perfect for microservices, real-time systems, and high-throughput backends.
Real-time systems?! I have never heard of anyone using Go for realtime systems because of its GC and preemptive scheduler. Seems like the sort of thing an LLM would slip in because it sounds good and nails that 3 item cadence.
> Built on top of Go channels → broken backpressure.
But then the example is about ordering. Maybe I'm being pedantic or missing the specific nomenclature the ReactiveX community uses, but backpressure and ordering are different concerns to me.
Then the Key Takeaways at the end just seems like an LLMism to me. It's a short article! Do we really need another 3 item list to summarize it?
I'm not anti-LLM, but the sameness of the content it generates grates on me.
I don't know. I've seen "realtime" used quite often in a sort of colloquial sense, where it means something fairly different from "hard realtime system" as an embedded systems person might use the term. I think there's a pretty large base of people who use "realtime" to mean something that others might call "near real-time" or just "continually updating" or something along those lines, where's there's no implication of needing nanosecond level predictable scheduling and what-not.
That's not to say that the article isn't AI generated, or whatever. Just that I wouldn't necessarily see the use of the "realtime" nomenclature as strong support for that possibility.
This is precisely the premise for their library: I don't have the mental context to fit all the boilerplate in, nor do I have the brainpower to sift through it.
Sure, assembly is readable: every line is one machine instruction. But that's way too many details. On the other hand, C++ templates are not enough details.
The way you approach this in Go (and I would argue in any other language) is by building small abstractions when and if it makes sense to do so. Not by introducing abstractions early, or in order to make a piece of code slightly easier to follow. A simple comment might help to explain tricky logic, but otherwise you shouldn't need explanations about core language features.
Abstractions are not free. They can be poorly written, leaky, abstract too much or too little, inflexible, increase overall complexity and thus cognitive load, impact performance, introduce bugs, etc.
So relying on them only when absolutely necessary is often the sensible choice.
Also, if possible, building your own bespoke abstraction is often preferable to using an external package. You can tailor it to your exact use case, without adding another dependency, which carries its own risks and problems.
This specific package seems designed to be syntax sugar over language features, which is not a good idea for future maintainers of the code that uses it. They would need to understand how this 3rd-party library works, even if the author claims it to be more readable, ergonomic, or what have you.
var result := []string{}
for i = 0; i < items.Length(); i++ {
item := items.Get(i)
if item < threshold {
result = append(result, converToString(item))
}
}
return resultI more readable than this code:
return items
.filter { it < threshold }
.map { convertToString(it) }
The argument is that everybody understand what the loop does, it's just a stupid loop. Bring back an an Algol 68, Pascal or C programmer from the 70s and they all understand what a for loop is. But my second example requires you to learn about filter and map and closures and implicit parameters like 'it'.Of course, once you do understand these very complicated concepts, the code above is far more readable: it clearly states WHAT the program does (filtering all values below the threshold and converting them to string) rather than HOW it does that (which nobody cares about). "readability" here is only counted from the narrow perspective of an imperative programmer who is not familiar with functional declarative data processing.
I feel the same about the "ro" examples in the OP. I don't particularly like that ro takes (mostly because Go forces its hand, I assume), like having to put everything in an explicit pipe, but I find the example far more readable than the pure Go example which combines loops, channels and WaitGroups. That's far worse than the loop example I gave in this reply, to be honest, and I really don't know why people say this example is readable. I guess you can optimize it a little, but I always found both channel and WaitGroups waitable, unreadable and error prone. They are only "readable" in narrow perverted sense that has somehow become prevalent in the Go community, where "readability" is redefined to mean: no closures, no immutable values, no generics, no type safety and certainly nothing that smells like FP.
So many Go developers ignore some tools because they consider them "not idiomatic".
But why not use abstractions when available ??? Did we forget to be productive ?
And the smallest possible team is the programmer and their future self.
Even then the hard thing is to predict what will be better for our future selves. Maybe we will be rusty in our Go skills or maybe we will have embraced idiomatic Go, and the approach that makes sense now will require our future self to puzzle through the code.
Of course maybe we will have kept programming the same way because it still feels like the better way and our future self will be efficient. But again that's only for the smallest possible team. If the team expands, then all bets are off.
My poor memory seems to recall them gaining traction ~10 years ago, but they've fallen hard off my radar.
My fear with adopting a library like this for Go is actually that it might end up being very unfriendly to the profiler once bottlenecks start occurring.
Brian Goetz even went as far as saying loom is going to kill reactive entirely: https://www.youtube.com/watch?v=9si7gK94gLo&t=1165s
I’ve worked in a few complex projects that adopted reactive styles and I don’t think they made things simpler. It was just as easy to couple components with reactive programming as it was without.
(snark aside, that syntax actually looks pretty nice).