I feel like Gradle is only relevant for Android. All other projects are fine with Maven (and I like a lot that Maven doesn't allow to code in the build config, any complex logic should be extracted to a custom build plugin, using real code. I just have PTSD after some build.gradle monstrosities).
It's just that Maven doesn't have a good core abstraction and it is not a reliable build system. Like even with base plugins, let alone with additional ones you can't be sure that a build actually picked up every change, you often have to do a double take and do a clean install instead to get some stale files cleared. This should never happen in a build tool and every other feature is secondary to this error.
That's why I defaulted to Gradle, which has its own idiocities (like tending to break the syntax on every second major version, but it's much better with the kotlin DSL), which at least 100% sound.
For more experimental/hobby projects I choose mill though.
Maybe, but customers DO want it, without realizing. I'm a decent DIYer, but I realize my wishes is not the same as a typical customer. Sadly, but customers vote with their wallets.
IMO, Kotlin coroutines are better of Go's goroutines, although they are a little different beasts to compare honestly (apples and oranges). Go inits goroutines on stack, but min size is 4KiB, so it's not trivial to grow them. Also you need to watch over and destruct goroutines manually to prevent memory leaks (using
var wg = sync.WaitGroup
defer wg.wait()
wg.Add(1)
go func() {
defer wg.Done()
}
)
And create a separate channel to return errors from a goroutine. Definitely more work.
Kotlin coroutines don't really exist. They're a (very neat) programming trick to support coroutine-like behavior on the JVM which doesn't support coroutines. If you look at the bytecode it produces, it honestly is a mess. And it colours your functions too: now you have be ever careful that if your function is running in a coroutine, there are certain things you should absolutely avoid doing in that function, or any function called by that function (like spinning the CPU on loop without yielding). In Go, you don't have to worry about any of this because the concurrency is built-in from the start.
Also I don't understand "it's not trivial to grow them". It is trivial to grow them, and that's why Go went this way. Maybe only 0.1% or fewer of use-cases will ever find any issues with the resizing stack (in fact probably the majority of uses are fine within the default starting stack size).
> Kotlin coroutines don't really exist. They're a (very neat) programming trick to support coroutine-like behavior on the JVM
Well, guess how coroutines are implemented in Rust/C++/everywhere else!
Nonetheless, I do think that java virtual threads are superior for the vast majority of use cases and they are a good default to reach for for "async" like code.
I recently worked on a Go project and my task was to make it more configurable at build time and start time (add plugins / addons, make it possible to reuse from other libraries and tools). Turned I had to create *Factory and *Providers, even though I did not want to.
It's easy to avoid "AbstractFactoryProviderBuilder" if everything is hardcoded. Try to make it reusable and extensible, and I bet you write one yourself.
> It's easy to avoid "AbstractFactoryProviderBuilder" if everything is hardcoded. Try to make it reusable and extensible, and I bet you write one yourself.
The first domino is opting for OOP. AbstractFactoryProviderBuilders are just the inevitable downstream consequence of that initial choice. No need for factories if you don't traffic in objects in the first place.
Bit of an extreme position, no? Languages without OO tend to end up reinventing it. Like the Linux kernel style of "big C structure with function pointers": that's just a vtable which you have to maintain by hand. Or, god help you, trying to do COM in C.
What's a good codebase that is very large but without either OO or pseudo-OO?
I totally agree one ought not use a non-OOP language for OOP. Right tools for the job and all that.
I work on large (but private, so I cannot share, sadly) FP-style codebases. The code style is stateless functions grouped in modules that operate on data-only structs in a factory line manner. Typed boundaries for correctness, short and maintainable functions for clarity. No member functions, so no vtables.
I've never seen such code need or use OOP design patterns. I'm just very gently pushing back against the idea all code devolves into OOP spaghetti in due course. It doesn't! There are better ways. :)
Mmm, no, I don't see why OOP has anything to do here (any examples?).
I did not use any inheritance. In general I would say OOP craziness faded long ago (in Java too), together with XML. Interfaces -- yes, are very much alive though.
I'm not using OOP to mean presently unpopular facets of OOP, to the exclusion of presently popular ones, I mean all of OOP. It's pretty hard to need factories, let alone factory factories, when one isn't using objects or classes.
A friendly joke in response to the claim that sufficiently complex code always ends up sprouting OOP abstract nonsense.
Unless you are using a strictly FP language, you are always basically using objects.
And all the stuff like factories and everything exist in FP as well, it's pretty short sighted to say otherwise. These are patterns that may or may not be useful in a given case. Sometimes a language feature can replace them (e.g. pattern matching ~ visitor pattern, but not even this is a full match see closed vs open hierarchy), but in most cases you just call them something else/don't have that problem because you are not writing that big of a software.
I do mostly use a strictly FP language, but it's trivial to use Java (C#, etc) as FP languages too. Just use classes as stateless function namespaces. FP Java is best Java.
> And all the stuff like factories and everything exist in FP as well, it's pretty short sighted to say otherwise. These are patterns that may or may not be useful in a given case. Sometimes a language feature can replace them (e.g. pattern matching ~ visitor pattern, but not even this is a full match see closed vs open hierarchy), but in most cases you just call them something else/don't have that problem because you are not writing that big of a software.
This is precisely the viewpoint that made me want to reply to the original comment. If you live and breathe OOP, you see OOP design patterns as solutions, because you've run into problems that they solve. If you stick to FP, you have no need of them. I appreciate this can be hard to imagine, but I respectfully suggest you have OOP blinkers on if you're so confident everyone is using factories that you attempt to size-shame FP codebases you've not even seen so you can remain in the right. :)
The purpose of factories is to decouple object creation and use. But FP already does this by never coupling behaviour and data in the first place. Think about it. If data is in a simple struct, with no member functions, ideally immutable, what kind of factory could you possibly need? How would it even work? What kind of problem could it possibly solve?
To say nothing of e.g. the visitor pattern, which is an approximation of functional styles using OOP. There is no need to define some small subset of behaviour outside a class, and then hook up boilerplate machinery inside that class to run that behaviour (ie the Visitor pattern) if all behaviour is always defined outside the class. The Visitor pattern solves an OOP problem that doesn't even arise in FP.
Are you sure about Go's garbage collector doesn't have pauses? AFAIK they are worse than modern Java's garbage collector [1].
I'm not sure it's even better than Java's, especially for modern ZGC (and you can choose your GC in Java). Definitely less configurable. I would say most of online comments about Java's GC are long outdated.
For example, in web servers a lot of work is request-response, so it's convenient to utilize generational GCs (so that each request data would fit into "young" generation). Similar to arenas, but without code changes. Go's GC is not generational though, so you should care about allocations.
Hell, I even had a use case where serial GC was actually the correct choice (small job runner process that needed to be extremely low memory overhead). It's nice having options, and most of those options are extremely good for the use cases they were designed for.
Ok, which one do I choose then, with what configuration? How much time do I need to spend on this research?
How do I verify that they are actually better? Is the overall performance of my program better? Because that's what I care about. I of course do include memory usage in "performance".
> I would say most of online comments about Java's GC are long outdated.
They are not. Feel free to look up literally any half-decent benchmarks. If Java's on par or better than any other language of note, check the memory usage. It's standard for Java to have 5-20x the memory usage for about the same performance. The memory floor also seems to be in the hundreds of megabytes. Ridiculous.
> For example, in web servers a lot of work is request-response, so it's convenient to utilize generational GCs (so that each request data would fit into "young" generation).
No, that's a job for arenas. Generational GCs are mostly a result of Java's limitations, and not a univerally good approach.
> Go's GC is not generational though, so you should care about allocations.
You should always care about allocations. In fact, the idea that you shouldn't is a big part of Java's poor performance and design.
No-one ever claimed that Java didn't use a lot of memory. The "comments about Java's GC" used to be about pauses, mainly.
Java programmers don't claim that the JVM is conservative with memory use.
That said, 5-20x.... nah. Maybe for a toy 'hello world' sized program, but not in real usage
Over 85 years and that's an inflation adjusted number. We give away more money each year (USAID/soft power efforts) than we spent on average on nuclear weapons. And neither of those items are of much significance on the US federal budget. Currently, social safety net programs are half of the federal budget and the total military budget is about 1/6th of the budget for reference (that's 2/3rd total between those two parts of the budget).
> And neither of those items are of much significance on the US federal budget.
$95 billion / year is $620 per US taxpayer.
> social safety net programs are half of the federal budget
I suppose you are referring to the big 3: Social Security, Medicare, and Medicaid. Those are programs that people pay for. In the same way that retirement savings, pensions, and private health insurance is something that people pay for.
But whatever, every dollar wasted to blow up people in another country can be excused because the federal budget includes programs that provide services to people in this country...or something. It is extremely revealing how some people are completely unbothered by some spending and are extremely bothered by other spending. The nuclear weapons don't bother you, but spending a bit of money to help alleviate famine for people in destitute countries is just unacceptable.
No. My point is not that something costs more than something else.
Look at a city and the traffic there we know that everything can either feel empty with only a ~8% decrease, or be completely gridlocked with a ~8% increase. Small adjustments in what we spend money on has a great effect. Being destructive is the easiest way to show this. If you bomb a hospital, does that cost ten million USD for the bombs or one billion USD to rebuild and handle loss of quality of life.
Naah, my non-english-speaking friends say that the keywords are less than 1% complexity of a programmer's job, so it really doesn't matter.
Also, in most languages you already can name variables/classes/members in any Unicode letters. So only "if/for/while" keywords and stdlib classes remain English. It makes little sense to translate those.
However, in the vast majority of cases, non-ASCII characters are rarely used for variable or function names during programming. This is because they can cause conflicts when using different encoding systems, and some automation tools fail to recognize them. Consequently, programmers in non-English speaking regions must invest more effort into naming variables than English speakers, as they have to translate all localized expressions into English.
When Toss, a Korean unicorn startup, announced that they would start using Korean for variable names within financial contexts, it sparked significant debate and a wide range of reactions among Korean programmers.
> This system was one of the oldest IT systems in NAV, and ran in production for 51 years, from when the National Insurance Scheme was introduced in 1967. In January 2018, Presys was put into production, which together with Pesys became the successor to DSF. At that point, DSF was also shut down.
The system is written in PL/I.
It's like the Apollo 11 code, but for social services.
reply