Functional Reactive Programming in Java
realm.io
realm.io
1. Horrible performance. Instead of for-loops you are wrapping code in ienumerables, yield returns, linq, and event handlers. This is not acceptable for game dev because of the constant freeze frames from the collection of all the garbage generated.
But how is this good for web dev either? You are either on the client using javascript which is already 200x slower than native, or on the backend where you could be dealing with 10s of 1000s requests at any moment so saving memory/performance is a priority.
2. 'Subscribe' methods makes behavior hard to pin down. Whenever i see an event in the code, i now have to assume there could be a subscription at some point at runtime. All I have for this is Ctrl+F search my codebase. Or write a boatload of tests to make sure someone doesn't subscribe something somewhere wrongly.
This creates a debugging nightmare. The more generic/dynamic code the more you can't see what's going on. Irony of an 'observable' is I can't observe it until I'm running my code which is too late.
3. Streams of data: to be fair, this is a nice feature. I've used it myself to flatten a tree and read as a list for example, where the reader would yield return a struct item { type=node|indent|dedent, node }. It's also nice to read and modify code like this, and quicker to refactor. But it is still wayy slower - your basically wrapping 'if statements' into 'filter(x => ...)' linq objects. The compiler can't optimise this stuff for branch prediction, not to mention the intermediate lists that are created, so for the readability you are losing soo much performance it's just not worth it. If there was a language that could compile down these streams into loops/ifs etc that could be a real win though.
A few years later I used to argue about direct function calls vs VMT lookups.
Once upon a time I used to DW for the whole block I was using. Then came NEW/DISPOSE. Nowadays I only care when the profiler tells me todo so.
I imagine you see where I am getting to.
FRP can be very different. With a large chunk of data and the wrong methods, you could be copying and re-copying that data over and over instead of editing it in place. This isn't just a 100% slowdown - you can go from 5 seconds to 500 seconds when converting an in-place data modification to FRP.
Basically O(n) vs O(2n) ? Optimize it later if necessary
O(n) vs O(n^3) - Uh oh! That's not to say that FRP is bad or you shouldn't use it, but never use a 'magic' mapping in FRP unless you know exactly what it is doing to the data and if it's going to copy or not (hint: it usually is).
Whether this matters is circumstantial, but for serious game dev it most definitely will.
I have thought about the same issues as you described. I see this as an interaction between two parties: producer and consumer. If the producer is so slow, that the consumer manages to ask 1000s of times for a product before it's created, than we have a case for reactive events.
Game programming is different: the rate at which the events are produced greatly overwhelm the consumer rate (60 fps is nice target for graphics).
There is one exception though! I have found a very annoying issue in a lot of games: they sometimes drop input. If you happen to play some fast action game and the game is crunching too much for the moment and drops FPS, also assuming that input events are polled once per frame, then a lot of input events might go unnoticed. Only the last input state for one frame is recognized by the game logic, but there might have been more than one.
So, in my opinion, input handling would be better handled in a pull fashion, where all the events could be processed immediately, for example: the rotation matrix could be updated off the game loop many times between one frame. I hate all those 3d viewers which loose all interactivity if the scene is too large. It doesn't have to be!
So, many input events are seen as a summarized version in the game loop. Not many of them.
aObservable.filter(n -> n % 2 == 0)
.map(n -> n * n)
.subscribe(System.out::println);
I love functional programming, but there's something that a FP programming language Haskell brings to it that you don't wont get in attempts to bring FP to other languages, and that's lazy evaluation.For example, when using FP in other languages I worry about how the functions I stack together to evaluate lists are implemented. I sincerely doubt that they have Haskell's optimizations and are actually continuously and unnecessarily iterating over everything in each function call multiple times.
Haskell doesn't actually do this even though that's how the code might read. So while I love declarative programming and writing and using functions that don't have side-effects I'm not sure that unless it's supported by the actual engine powering the language that it's the best idea. In fact it may be a bit cargo-cult programming, it looks like FP, but it doesn't actually work like FP.
Additionally, regarding the observer pattern in my experience that leads to tightly coupled code where you can't always easily trace the line of execution or separate it out later when you need to. It's like global streams of communication, sort of like using global variables to pass state around. I can't tell from the article if they've addressed that.
https://donsbot.wordpress.com/2008/06/04/haskell-as-fast-as-...
I'm only speaking for languages I know:
- C# LINQ API via IEnumerable<T> is lazy (aside from a few like Min/Max)
- F# is lazy for any Seq functions or the Lazy() computation
- Clojure is lazy for Sequence functions
You can verify all three by looking at the source on Github if you'd like.
That's definitely a danger. When I use the simpler (compared to Rx) Observables in Knockout.js, I make sure to keep my ViewModels small and single-purpose, which helps address this problem.
When I used RxJS with Cycle.js, you can kind of do something similar using widgets, but it seems like it might be harder since a widget in Cycle isn't supposed to have any domain logic. I know Cycle's author recently posted a fairly large example in use at his company's site, so maybe that shows the way to structure things.
There is nothing to iterate over in Rx. An Observable is NOT a list. It can be an asynchronous sequence, for instance a sequence of mouse clicks, or events from a websocket. All it does is call onNext on the subscribed Observers. So it's not as if chaining a map and then a filter iterates over a collection twice. In your example, when the source Observable emits a new value, it calls onNext for the FilterObservable, which then calls onNext for the MapObservable.
I really like Rx and I am using it in projects I'm currently working on, but there's still that basic discomfort that it's not purely functional.
It's just that kind of library that solves a lot of problems, which people don't even know of (or do not want to). I have very little experience with Rx, but still I found some really useful applications for it:
1. Jumping between threads back and forth. This is a typical requirement of a UI Toolkit (Java Swing, Android). I don't remember the exact details, but some special combinators (subscribeOnEdt) were provided out of the box by the library (was using rxScala then). You just connected button to some action, which result was displayed in a text field. With rxScala for Swing it was an extra function call. In plain JDK it's an extreme pain (SwingWorker, SwingUtilities.invokeLater and custom Runnables or threads etc.)
2. Edge case handling in abstractions. Let's say you want to implement a real time moving average of exactly 3 points, but discarding 10 first results. In Rx it is something of a "Hello World" level of complexity. The same thing, in plain Java, which also handles the edge cases correctly is almost always implemented buggy.
3. Event bookkeeping. If you happen to use push model a lot, a funny thing occurs. In case of UI applications some people call it a "glitch". In electronic engineering this is a race condition (https://en.wikipedia.org/wiki/Race_condition). It's a very fundamental problem, which is solved in rx implementation. The merge operator takes care, that the events fired are traversing the dependency graph in a topological order. That way, it is way harder to create that bug (not impossible though).
4. Error handling by default. This is also very very useful. A typical programmer in Java implements handling for all happy cases, few error conditions and most often than not, misses a lot of other error cases. "Who would notice? Besides, exception handling in Java is really ugly." So yeah, they don't write it. With RX the error condition handling is always present, by the means of termination message. Every combinator has a clearly defined semantics for handling those onComplete events. It's just a matter of reifing exceptions and defining stream for error events. You could do it in Java of course, but the amount of boilerplate which is killed by handling this in rx is very impressive.
Nobody said it was new. Do you really think all developers know everything , every paradigms , techniques, frameworks in existence ? no .
Do you like FRP ? if yes then why would you complain about FRP vulgarization ? about yet an FRP blog ?
Some things might seem obvious to you. Well there are not obvious for everybody because they have other things to worry about in their respective domain. Don't be arrogant. If you like FRP then you want more people to embrace this paradigm. Something will always be new to somebody.