Lift Operator
After several chapters of Getting Started and React Epic in Action, you might wonder why there's another chapter on what the lift
function is and how it works.
The fact that we have discussed and use lift
operator so frequent that we haven't have chance to breakdown on lift
operator. So here is how we start:
My first ideal of the lift
function is simple, is to create a function for lifting the pure logic functions into RxJS. So i come up with two functions without actually knowing clearly how it works:
I know, the second lift is so miraculous. But it can not against the fact that the second is the correct one for my situation. You may question me why.
The fact is both of the lift functions have their point of view about how a lifted function should be. To take a look closer, let consider an example of three arguments functions:
What is the different between combineLatest
and the combination of switchMap
and map
. Why we only run map
on the last argument?
So i take more time investigate deeper into the effect of these three functions and the result is fantasy. combineLatest
will emit everytime any of its arguments emits a value. So i will consider, it's fully dynamic. What about switchMap
and map
? I consider switchMap
is static and map
is dynamic. This may a little bit tricky against the fact that switchMap
changes everytime its argument emits a value. Consider the following example:
People usually tends to be tricked into the ideal that this is a loop. But it doesn't.
I will tell you something. The change doesn't result in emitting values. In another word, it only tell the core stream that arg1 and arg2 has changed and switch the stream based these two value, but does not emit any value at all. The real stream that emits values is the stream come from arg3. That why i consider switchMap
is static and map
is dynamic.
So why does this important. If you notice, the above pattern of todos
and addTodo
will stretch across all other state machine pattern. What is the state machine pattern formally? And if you have another notice, why we have two nesting switchMap
in the three argument example?
The fact is in the offical implementation of lift function there're only two arguments:
If you notice, we don't care how many nesting switchMap
are there, the final result will only depend on the last argument. The same think works with map
(You can have multiple nested map
or mergeMap
either). So how to compact its form? It's easy:
So the final form will only contain two arguments. And if you have three or more arguments, depends on which argument that emits values, we can combine them together! For example:
To make you better understand of how it works, i will better rewrite its implementation with Ninja Comments:
Huh, what the heck! This sounds familiar! 🤔 Does it reminds you of Redux? Are you missing something? The fact is that all application implementations consist of two things: the app own state and the external actions (or outer event space). The fact, it's not remind me of Redux but Haskell State. I have had a hard time taking deep investigation into Haskell State and don't understand anything about how it works and why it even exists! Then i come up with one diagram that helps me a bit about understading it:
Haskell states run inside a closure (a state loop), runState
project values and actions into state loop. It triggers the state change, toggle between states. And finally, with some kind of magical way, getState
project the state value out of the loop.
So if i'm not wrong, the lift function works the same way with runState
(Please correct me if i'm wrong). So if you don't might, i will translate the following piece of code:
Into Haskell implementation:
Actually you will see i embedded state and action arguments into the lift operator for convenient:
The real meaning of this decision is that an operator usually being attached with a state source and an event source. So the result will be a RxJS Observable other than a lifted RxJS operator. If you wonder, the lift function is smart, so that writing the lift function in the both ways are valid:
There's only one caveat is this might lead to the inconsistency of your source code. So the best recommendation is to use the embedded version all the time and only use the original semantic version when necessary.
More on State and Actions
"Don't use lift operator if you don't really know what it does". Just kidding, we have so far discuss and use lift
so frequent in our document but our app still looks good and works like a charm. But what i wanna tell you is different. What i want to tell you is not whether your app works fine or not, but how much you understand about the architecture behind the scene of the lift operator behind your app. For example:
Is it even considered a state by the way? What the state really is? You often see that that output of the reducer (a state and an action) is a state (not an action) Why? Because our intuition says that state is static and action is dynamic so that the result should be dynamic? Should we put the result of an reducer into another reducer? If yes so where should we put it into? The state or the action?
It looks like the question the sum of an odd number and an even number is an odd and an even. The fact if you breakdown on the reducer implementation, you might missing something:
So here we come up with a formular of what state really is:
So that you know how the interval function works. It works by two things, the initial counter, and the clock tick event:
However, there will be much more complicated situations than this one so for more information about the hard cases in RxJS, please visit the next chapter: Execution Context in RxJS.
Last updated
Was this helpful?