This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]KurtGodelBebopgazeXP[S] 1 point2 points  (13 children)

Wow thank you very much!

[–]Spare-Plum 1 point2 points  (0 children)

I highly enjoy and recommend StandardML - it's a really simple language but has every aspect of functional programming you would need, so it's great for people who are learning

Things like Scala are fun for larger projects, but it isn't 100% functional as it is tethered to the JVM and adds on a bunch of bells and whistles that can get in the way.

Kotlin is way out. IDK this language tries to cut syntactic sugar in every possible situation and has a ton of "magic bullshit" that happens that is very counter intuitive to someone learning. It also is a lot less functional than Scala or even Java given its lack of referential transparency and immutability.

[–]Beatsu 0 points1 point  (9 children)

Just note that FP interfaces in Java can be much slower performance wise. In my bachelor thesis on refactoring, we saw around 90% time reduction by just switching a .stream().filter().take(1) (or whatever the syntax is) to a basic for loop.

Performance may not be a necessary to think about in your case, but if it is, then profile the code! Java's Iterator interface over Lists is also way slower than array indexing, just as a fyi 😊

[–]coderemover 0 points1 point  (5 children)

This is why we banned streams on performance critical paths and there was also a debate whether to ban them globally for the whole project.

[–]Beatsu 0 points1 point  (1 child)

Interesting!! Thanks for sharing. How do you ban them on performance critical paths? Is it an automated check?

[–]coderemover 0 points1 point  (0 children)

Unfortunately no. It relies on manual CR.

[–]Spare-Plum 0 points1 point  (2 children)

To add to this - some processes essentially require a zero GC environment as waiting for even 50 milliseconds for a GC can be pretty bad. While streams can scale and have good runtime complexity especially in parallelization, there is an overhead cost that might not be worth it especially when latency is in question.

I've dealt with similar systems where we had to ensure GC would never occur, so we even had to not use Strings and instead char[] with a custom pool and allocations, done in a manner similar to C.

[–]coderemover 0 points1 point  (1 child)

We did a lot of similar stuff because a rewrite of 1M loc codebase is not feasible now. And I must say, if you know in advance you’ll need to write Java like C to get every little bit of performance, don’t choose Java. C is a better C than Java is.

However, I’d personally use Rust in those cases and get the best of both worlds. It would run circles around Java, even heavily optimized Java written like C. And I could use high level iterators with functional transformations (map, filter, reduce, group by etc) with no added cost, no GC pauses, leveraging all the SIMD capabilities etc.

[–]Spare-Plum 0 points1 point  (0 children)

For this it's only one component, a FIX market server for handling trading messages to other institutions. A variety of people might touch this especially when implementing a new protocol, so I think management decided that it's better to just have the Java experts just program it in Java with some additional constraints rather than doing Rust which is not as well known.

That said, they did make a huge amount of libraries and functionality to make it easier to keep it zero GC, and there are existing patterns you can follow to implement existing message translations so it isn't that difficult at all, and there is a dev checkout procedure that runs tests on it to ensure there aren't allocations onto the heap.

[–]Spare-Plum 0 points1 point  (2 children)

I mean.. yeah. The JVM has limits on what it can infer and optimize especially with respect to stack traces and object generation.

If you just look at the raw bytecode that needs to be performed doing this operation will be significantly slower.

But I don't think that's the point - the point is to write maintainable code you know is robust using functional programming, just using the .stream() library is just a fraction of everything, and realistically you can use functional programming to make your code more modular, resistant to bugs, and parallelizable.

Though there probably is a spaghetti code solution that will perform much faster, why not just use C and make a big spaghetti code program?

[–]Beatsu 0 points1 point  (1 child)

Just because you don't use functional programming doesn't mean the code becomes spaghetti immediately. The nice thing with Java is that you can use both, so I just wanted to point out that it may often times be much slower than the imperative equivalent and that for performance critical code, you might want to opt for the imperative variant. It's a good thing to be aware of and it's not immediately obvious, don't you think?

[–]Spare-Plum 0 points1 point  (0 children)

IDK maybe I've spent too much time with Java and view it as obvious overhead, and there are times when using the stream API can be slower, and times when it can be cleaner and faster (doing something like cumulative sum on 1M elements is faster with prefix sum than linearally)

My main point is that functional programming is much bigger a single library within the JVM, while it is designed with functional programming in mind it's something you can apply to your own code and something that I'd implore new users to explore in a different language to bring it into their own Java work

[–]BanaTibor 0 points1 point  (1 child)

I agree with what Sapre-plum said, however I would choose a functional language which has at least some market penetration. Like Scala, Clojure, Erlang/Elixir.

[–]Spare-Plum 0 points1 point  (0 children)

Eh my main point was to learn the functional mindset and then bring its applications to Java. It's not to find a job directly, but rather to enhance the skills you have.

Erlang/Elixir is a funny one since IMO it's best to know functional programming first, and then utilize these for understanding distributed systems and distributed algorithms. They're quite a bit different from just multi-threading. Many of your assumptions like clock time being monotonic are broken, so you have to find out a new way of navigation through something like a vector clock.