Behavior programming simplifies the task of handling underspecifications and conflicting requirements by enabling the addition of software modules that can not only add to but also modify existing behaviors.
high level concepts
I will first explain the high-level concepts using the example of two React components MovieList and MovieCount. One displays a list of movies, the other how many movies there are. Then I’ll take a look at how behavioral programming actually works.
Both components receive data from the same HTTP URL. They were developed by two separate teams in a larger organization. When we render both components on the same page, we have a problem because they make the same request.
In the above example, we stepped inside the MoviesCount component. We waited and requested something happen. And, more specifically for behavioral programming, we stopped anything from happening.
Since we were trying to prevent both requests from being fired, we prevented the FETCH_COUNT event from being triggered (since the same data was already received by the FETCH_LIST event).
Adding functionality to existing components without modifying their code is a novelty of the behavioral programming paradigm.
Intuitively, this could allow for the creation of more reusable components.
In the rest of the article, I’ll go into more depth about how Behavioral Programming (BP) works, specifically in the context of React.
Rethinking programming flow
To achieve the above functionality, we need to think about programming behaviors a little differently. In particular, events play an important role in organizing the synchronization between the different behaviors we define for our components.
As expected, the first behavior executes. Once this is done, the second behavior continues. However, the new specifications for our component require us to change the order in which both events are triggered. Instead of triggering ADD_HOT three times and then ADD_COLD three times, we want them to interleave and trigger ADD_COLD right after ADD_HOT. This will keep the temperature somewhat stable.
We change the order in which things are executed, without modifying the code of previously written behaviors.
The process is summarized in the graphic below.
Each B-thread (behavior thread) lives on its own and is unaware of other threads. But they are all interconnected at runtime, which allows them to interact with each other in very new ways.
The generator syntax is essential for the functioning of a behavioral program. We need to control when to proceed to the next yield statement.
back to feedback
How can these BP concepts be used in the context of feedback?
Turns out that through higher-order components (HOCs), you can add this practical idiom to existing components in a very intuitive way:
Here we are using WithBehavior from the B-thread library to make CommentCount a behavioral component. Specifically, we are making it to fetch the comments and display the data after the data is ready.
For simple components, this may not be such a game-changer. But let’s imagine more complex components, with lots of logic and other components inside them.
When we use this component in our app, we want to interact with it. Specifically, when a movie is clicked, we don’t want to start the movie immediately, but instead we want to make an HTTP request, show other data about the movie, and then start the movie. Huh.
Without changing the code inside the <Netflix /> component, I’d argue this would be impossible to achieve without a practical component.
Above we’ve created a new NetflixWithMovieInfo component that modifies the behavior of the <Netflix /> component (again, without changing its source code). Adding to the above behaviors such that MOVIE_CLICKED will not trigger MOVIE_START immediately.
Instead, it uses a combination of “waiting while blocking”: a wait and a block can be defined within a single yield statement.
The diagram above explains in detail what is happening within our behavioral components. Each little box within the components is a produce description. Each vertically dashed arrow represents a behavior (aka B-thread).
Internally, the behavioral implementation will begin by looking at all yield statements for all B-threads at the current synchronization point, which is represented by a horizontal yellow line. It will continue to the next yield statement within the B-thread only if no event in the other B-threads is blocking it.
Since nothing is blocking the MOVIE_CLICKED, it will be requested. We can then continue to the next yield statement for Netflix behavior. At the next sync point, the B-thread on the far right, waiting for MOVIE_CLICKED, will proceed to its next yield statement.