📜 ⬆️ ⬇️

Object Reactive Programming

Object Reactive Programming


Dmitry Karlovsky from SAPRUN represents ... mmm ...


This is the text version of the same-name speech at FrontendConf'17 . You can read it as an article , or open it in the presentation interface , or watch a video .

I'm tired ..How will the ODP help?
... write a lot, and do little?Write a little, do a lot!
... debug simple logic for hours?Reactive rules ensure consistency!
... asynchronous?Synchronous code can also be non-blocking!
... that everything is stupid by default?PIU optimizes data flows automatically!
... functional puzzles?Objects with properties - nowhere easier!
... that the application crashes entirely?Let the parts fall - the very rise!
... juggle waiting indicators?Indicators of expectations let them appear where necessary!
... two-way binding?Bilateral binding needs to be cooked properly!
... saw the reused components?Let components be reusable by default!
... always catch up?Get ahead and lead!

Commercial break


SAPRUN


Hello everyone, my name is Dmitry Karlovsky. I am the head of the web development group for SAPRAN. Our company is the largest SAP integrator in Russia, but lately we have been actively looking towards developing our own software products.

$ mol - reactive to the bone


One of them is a cross-platform open source web framework for quickly building responsive interfaces with a talking name "$ mol" . In it, we maximize the use of Object Reactive Programming, which I will discuss further ...

$ mol


What are we going to do in the evening? Let's try to win retail!


Let's imagine that we decided to open an online store selling toys. And we want to do everything not as anything, but in a stylish, fashionable, youthful, fast, flexible and reliable way ..

toys.hyoo.ru


Catalog


We have a lot of toys, and we had to start selling yesterday. Therefore, we want to do everything as quickly as possible, but not about ... losing the user experience.

We can download the basic data of all the toys on the client device and allow the user to browse the catalog quickly, without network delays.

Catalog of various toys


Filtration


Leafing through our catalog is, of course, an exciting activity, but the user would like to limit the selection to only those toys that are of particular interest to him at the moment. Therefore, we add filtering.

Catalog with filter


For large amounts of data, a complex filter may overlap for a long time, so to ensure responsiveness, we would not want the filtering to repeat once again when it is not necessary.

For example, if we filtered by size, then when the number of reviews changes, there is no point in re-filtering. But if filtered by the number of reviews ... well, you understand, right?
')
It is important for us to know from which specific properties of which particular goods the result of filtering depends, in order to restart it when changing only those states that actually affect the result.

Sorting


The user usually wants to view the toys not in an arbitrary order, but in a particular one. For example: in order of increasing prices or in order of decreasing relevance. Therefore we add sorting. Again, it can affect different properties of goods and be quite heavy on large amounts of data.

Catalog with complex filter and complex sorting


Obviously, the re-sorting will need to be done only when the sorting criterion is changed ... or the properties of the goods we sorted by. But not any products, but only those that meet the filtering criteria. And, accordingly, when changing this very criterion. And also, when changing the properties of the goods for which we filtered. AND...

Accounting for all dependencies


If you try to describe in the code all the dependencies between all the states, then a combinatorial explosion will occur and your hands will be torn off.

Diagram of all dependencies between states


Here we have described only 2 steps of transformations, but in a real application there can be more than a dozen.

To curb this exponentially growing complexity, and Reactive Programming was invented. Without it, you cannot make any complex application fast, stable and compact at the same time.

Is everything rendering?


If you display all the data that you have prepared, then the algorithmic rendering complexity will be proportional to the amount of this data. 10 products are rendered instantly, 1000 - with a delay, and if 10,000, the user will have time to go and drink some tea.

Graph with linear progression


Or not everything?


If a user has such a screen that no more than 10 products fit into it at the same time, then visually there will be no difference for him - whether you will render the whole 1000 or only 10 of them, and as you scroll, render the missing. Therefore, no matter how fast you have React template engine, it will always play out for responsiveness to a lazy architecture, which is much less dependent on data volumes.

Graph with logarithmic and linear progression


Display only visible


If the sizes of the goods cards are known to us, then, knowing the height of the window, it is easy to understand which products are not exactly visible, and which can at least slightly, but fit into the visible area.

Cutting chart visible part of the list of goods


Cut from the huge list of items from the first to the tenth - a smooth operation. But only if this list is stored somewhere in the cached form. If in each frame we get it from the source data by filtering and sorting, then there is no question of any smooth scrolling.

Applying changes to the DOM


Ok, the data we have prepared, it remains to show them to the user. The solution to the forehead is to remove the old DOM tree and grow a new one. This is how all HTML template engines work.

Redrawing the DOM is long


It is not only slow, it also leads to a reset of the dynamic state of the nodes. For example: the scrolling position and the open flag of the select. Some of them can then be restored programmatically, but not all.

In short, reality stubbornly does not want to be a pure function. For the application to work as it should, you should, if possible, change the existing state, and not just create a new one. And if you can not win - head!

Virtual dom


How to pull a snake on a hedgehog? Right, let's generate a new DOM tree for the entire application, and then React the special library will compare its new and old versions, and apply the differences to the DOM tree that the user actually sees.

Virtual home for every sneeze - it's slow


It sounds like a crutch, is not it? How much work has to be done only in order to change the value of the text node, when the string property has changed in one of the models.

Direct dependencies


And what would the work of the most effective solution look like?

Direct dependencies. What could be more effective?


Everything is simple - direct links are established between the source data and their display. When one state changes, only those dependent on it change. And it works not only between the so-called "model" and "display", but between any dependent states, starting with a record in the database on the server, through a bunch of intermediate states and ending with a home-node in the browser. This is the essence of Reactive Programming, and not in the template engine with a consonant name that is sold to us at each conference.

And so come down!


And so come down!


You can say that I exaggerate, and specifically in your project there will never be so much data and heavy processing, and you will run your application only on powerful workstations, and not on a sickly Chinese sneaker in power saving mode.

Sierpinski Triangle on ReactJS


But for this I have a simple consideration. In order for the above application to work at a speed of 60 frames per second, it must have time to perform all operations, starting with data preparation and finishing with their rendering, in just 16 milliseconds. And it is very easy to exceed them even in a trivial application on a powerful computer.

nin-jin.imtqy.com/sierpinski/stack.html


Here is a well-known demo created by guys from Facebook, showing how hard the complex applications on React are stupid. They are now trying to solve this by spreading out calculations over several frames, which gives the cherished 60 frames per second, but leads to visual artifacts. The fundamental problem they have remains unchanged - the virtual DOM requires a heap of extra calculations for each other.

Sierpinski Triangle using the ODP


The PPR, on the contrary, allows minimizing the amount of computation by automatically optimizing the data flows from their source to their consumer.

mol.js.org/perf/sierp/


As you can see, an equivalent implementation using reactive programming shows much better performance, even without blurring frame calculations.

Paradigms


Let's finally add some theory ...

What is Object Programming? Its main feature is the integration of data and functions for working with them within a single abstraction with a relatively simple interface - the object.

What is Functional Programming? Here the whole program is described as a heap of pure functions that do not depend on the variable state and do not change any states themselves.

Finally, what is Reactive Programming? Here you describe the rules for obtaining one state from another in such a way that a change in one state leads to a cascade change in dependent states.

Object, Functional and Reactive


For many, Reactive Programming is strongly associated with Functional, however, it is much closer to Object Objective, as the main actors in Reactive Programming are mutable states.

Push (FRP)


There are two fundamentally different ways of implementing reactivity.

The first is all sorts of bacons , RXs and other stream punk, also known as Functional Reactive Programming. Its essence is that you explicitly get the so-called streams, on which your condition depends, add to them the function of calculating a new value. And the value thus obtained is already pushed into all dependent streams, and they already decide what to do or not with this value.

const FilterSource = new Rx.BehaviorSubject( toy => toy.count > 0 )
const Filter = FilterSource.distinctUntilChanged().debounce( 0 )

const ToysSource = new Rx.BehaviorSubject( [] )
const Toys = ToysSource.distinctUntilChanged().debounce( 0 )

const ToysFiltered = Filter
.select( filter => {
    if( !filter ) return Toys
    return Toys.map( toys => toys.filter( filter ) )
} )
.switch()
.distinctUntilChanged()
.debounce( 0 )

- , . : , .

. : , . , , .

, . . . . , , , .

()


, , . , , — $mol_mem.

class $my_toys {

    @ $mol_mem
    filter( next ) {
        if( next === undefined ) return toy => toy.count() > 0

        return next
    }

    @ $mol_mem
    toys( next = [] ){ return next }

    @ $mol_mem
    toys_filtered() {
        if( !this.filter() ) return this.toys()

        return this.toys().filter( this.filter() )
    }
}

ORP , ? , , , , , .

, .


, .

@ $mol_mem
sorter( next ) {
    if( next === undefined ) return ( a , b )=> b.price() - a.price()

    return next
}

@ $mol_mem
toys_sorted() {
    if( !this.sorter() ) return this.toys_filtered()

    return this.toys_filtered().slice().sort( this.sorter() )
}

, - , .


, . , .

@ $mol_mem
toys_visible() {
    return this.toys_sorted().slice( ... this.view_window() )
}

children, , . .

children() {
    return this.toys_visible()
}


render, .

@ $mol_mem
render() {
    let node = document.getElementById( this.id() )

    if( !node ) {
        node = document.createElement( 'div' )
        node.id = this.id()
    }

    /// Node updating here

    return node
}

, DOM-. , . .

. , , , DOM-.

?


. , . , — , .

toys.hyoo.ru/#luck=.9


- , , . , .


DOM- try-catch , . — .

try {

    /// Node updating here

    node.removeAttribute( 'mol_view_error' )

} catch( error ) {

    console.error( error )

    node.setAttribute( 'mol_view_error' , error.name )
}


? CSS , , . , .

[mol_view_error] {
    opacity: .5 !important;
    pointer-events: none !important;
}

:


. , .

namesakes_message() {

    /// Serial
    const user = this.user()
    const count = this.name_count( user.name )

    return this.texts().namesakes_message
    .replace( /\{count\}/g , count )
}

, , .

, ? : , javascript , .

:


.

namesakes_message() {

    /// Parallel
    return Promise.all([

        /// Serial
        this.user().then( user => this.name_count( user.name ) ,

        this.texts() ,

    ])
    .then( ([ count , texts ])=> {
        return texts.namesakes_message.replace( /\{count\}/g , count )
    } )

}

, , . , .

:


, , .

/// Serial
async namesakes_count() {
    const user = await this.user()
    return await this.name_count( user.name )
}

async namesakes_message() {

    /// Parallel
    const [ count, texts ] = await Promise.all([
        this.namesakes_count() ,
        this.texts() ,
    ])

    return texts.namesakes_message.replace( /\{count\}/g , count )
}

"", .

, , .

:


, , ?

@ $mol_mem
namesakes_message() {

    /// Parallel
    const texts = this.texts()
    const user = this.user()

    /// Serial
    const count = this.namesakes_count( user.name )

    return texts.namesakes_message.replace( /\{count\}/g , count )
}

. , — . , , . , , .

:


, , , , - , — .

, user.name, .

@ $mol_mem
namesakes_message() {

    /// Parallel
    const texts = this.texts()
    const user = this.user()

    /// Serial
    const count = this.namesakes_count( user.name ) /// <-- first yield

    return texts.namesakes_message.replace( /\{count\}/g , count )
}

:


, . .

/// Before
@ $mol_mem
toys(){ return [] }

/// After
toys_data() {
    return $mol_http.resource( '/toys.json' ).json()
}

@ $mol_mem
toys() {
    return Object.keys( this.toys_data() ).map( id => this.toy( id ) )
}

, — , , .


. , DOM-, . , . .

[mol_view_error="$mol_atom_wait"] {
    animation: my_waiting .25s steps(6) infinite;
}


. , .


, — , . , , . "". " , ".

, , , , , , .

, . , , . , ?


, , , ?


:



:



:



:



, ?

Angular. , watcher-, , - .

, , " , ".


?


… ?



,



Facebook - FLUX, , — — "".

, — . , .

, .

:


, , , , . , , . . ?


:


— . — "". , — .


, .

:


, , .


, . FLUX-, .


class $mol_string {

    hint() { return '' }

    @ $mol_mem
    value( next = '' ) { return next }

    // ...
}

— . : hint — , ; value — . value. , .


. , , hint .

const Name = new $mol_string

Name.hint = ()=> 'Batman'


value , .

let name = 'Jin'

Name.value = ( next = name )=> {
    return name = next
}

, value name, — name .

?


, , , .


.

, .

, : .


, , , . , , .

    @ $mol_mem
    Name() {
        const Name = new $mol_string

        /// Setup ```Name``` here

        return Name
    }

, , - , . .


, , , .

        /// Setup ```Name```:

        /// One way binding
        Name.hint =  ()=> this.name_hint()

        /// Two way binding
        Name.value = ( next )=> this.name( next )

, , , . , , , .


— .


, , .


.


.


— .


— .


: . . — "" , , , — , .

. , , , , . , . .

?


. - , .

: $mol_mem, VueJS, MobX, CellX, KnockOut


: toys.hyoo.ru


: github.com/nin-jin/toys.hyoo.ru


: nin-jin.imtqy.com/slides/orp


: github.com/nin-jin/sierpinski

Source: https://habr.com/ru/post/330466/


All Articles