An Introduction to the Behavioral Programming Paradigm

Alec Sanchez
13 min readJan 30, 2021

--

For the past few years, I have been designing a paradigm, one that would solve all of the issues I have with modern software design and modern Object-Oriented Programming (OOP). I wanted to remove the restrictions imposed by other paradigms, allowing developers to take things into their own hands. Note that this article is not a bashing or critique of OOP by any means. This is simply teaching the new paradigm, but does talk about areas where it has further benefits over modern OOP. All paradigms are tools to be used in the right place. Now that being said, this is what I have come up with.

Behavioral Programming (BP) (not to be confused with the thread-based design philosophy) is the paradigm I have created to solve these issues. It completely removes classes, replaces methods with “traits”, and interfaces becomes “behaviors”, each of which are their own implementations. That was the initial design of BP years ago, but as you will see, the paradigm has grown further outside of OOP’s influence, becoming its own programming ideology and design philosophy. This post will go in depth about BP, discussing the very basics to even possible implementations of it.

Note that BP has yet to be implemented in a language. This is a theoretical design. That being said, I will be designing languages in the future that do feature Behavioral Programming. I wished to get this out into the world first for critique and possible improvements or innovation within the paradigm that could help to strengthen it even further.

Basics of Behavioral Programming

BP is broken up into two, essential parts: Traits and Appointments. Behaviors, ironically, are not essential to BP, but are highly encouraged in implementations. “Trait Programming” just doesn’t roll off the tongue quite as well, wouldn’t you agree? Though this name does conflict with a thread-based design paradigm, resources on it are far from numerous, and even the original documentation on it is no longer available. For that reason, after much deliberation, I decided to keep the name “Behavioral Programming” as it fit the paradigm best. If ambiguity does arise, this form of Behavioral Programming can be referred to as “Behavioral Trait Programming”, or BTP. From here on, key terms unique to BP will be in bold.

Traits and Appointments

To begin, a trait is, in essence, a method as well as a mixin (though there is much more nuance, especially in regards to the ideology). However, instead of taking a pointer to a whole set of data (e.g. struct/record), it typically takes a pointer to a field within the data (it is not disallowed to pass the data itself, as this is implementation-specific). Traits do not necessarily have to mutate the data supplied. Imagine a trait as an isolated, computational unit. In order to use a trait, you must appoint it to the data (this is where the mixin-side of traits appear). Appointment entails some compile-time or runtime functionality to allow data to use a trait or a set of traits. Traits and appointment offer a huge amount of power in terms of refactoring, code reuse, debugging, reducing or removing most boilerplate code, unit testing, and immediate loose coupling of implementations, as well as their relation to the data. These benefits will be explored near the end of the article.

This is a simple example of appointment and use of traits:

struct Numbers:
a: int,
b: int,
# Create a method which adds to the supplied field
trait AddToField ('number: int, other: int):
'number += other
function Main:
# Create the Numbers struct where a = 0 and b = 2
# then appoint the AddToField trait.
numbers := Numbers(0, 2)[AddToField('a)]
numbers->AddToField(1)
# numbers.a == 1
numbers->AddToField(numbers.b)
# numbers.a == 3

In this case, the apostrophe is used to reference a specific field for appointment. The square brackets are one possible way that appointments could be implemented syntactically.

What Does This Allow For?

In BP, modularity is the main focus. You can use any implementation, and even swap out parts of implementations, if the traits are loosely coupled properly. BP is built upon this, to be as least rigid and as most open as possible, for both interpretation and use. Here’s an example: say that there was a part of a library that needed to be replaced with another, maybe just a single trait, or even more. In BP, this could easily be done with de-appointing the original trait, then appointing the new one. The traits can still interact with one another (and even depend on appointed traits if the language implementing BP allows for that). And if the other traits rely on some form of functionality from the original, changing the appointments on your end doesn’t affect their code, and vice versa. YOU are in complete control of what functionality and implementations you use, unlike Object-Oriented Programming, where you are constrained to using the implementation of another, having to edit their class, having to create your own class, configuring dependency injection, extending their classes and adding casts and/or conditional branches, or anything else that adds complexity to the program. Essentially, BP allows you to swap implementation without having to touch others’ code and without nearly as much complexity. This is entirely possible with carefully-designed systems, but BP does this out of the box without any preemptive design. The data remains the same, but the functionality surrounding it changes.

BP automatically loosely couples data from the functionality and even the functionality from the functionality.

Additional Constructs

There are additional constructs to BP that heighten the experience of using it and give developers more tools to design and build with.

Behaviors

Behaviors are groups of traits that share the same data. This first defines what fields it needs, then all traits within it derive their fields from the behaviors. This allows for grouping multiple related traits together, making appointment much simpler and less verbose, but without removing the ability to de-appoint traits and replace them with new ones. Below is an example of a behavior:

# Create a behavior that has an addition and subtraction trait
behavior Calculator ('number: int):
trait Add (other: int):
'number += other
trait Subtract (other: int):
'number -= other
function Main:
# Assign foo as 0 and appoint the Calculator behavior
foo := 0 [Calculator(')]
foo->Add(10)
#foo == 10
foo->Subtract(10)
# foo == 0
# De-appoint the Subtract trait
foo [-Calculator.Subtract]
foo->Subtract(10)
# Error (compile-time or runtime)
# as Calculator.Subtract has been de-appointed

In this case, foo was appointed Calculator to itself (the '). When the traits within the behavior were called on foo, it changed foo’s state. (Note: traits do not have to change state or rely on mutable states). When the Subtract trait was de-appointed, it leaves the scope of the data and cannot be used further unless the trait, or one of the same name, is appointed.

Characters

Behaviors can be expanded further with Characters, which themselves group behaviors and other behavioral constructs. This is the final chain of an initial three-part hierarchy for program design. Imagine this as a union of behaviors, as well as traits and potentially even other characters, where each is appointed all at once. This mainly allows for less verbosity. Here is a rudimentary example of a character:

# Create a behavior which only has one trait to add
behavior AddBehavior ('number: int):
trait Add (other: int):
'number += other
# Create a behavior which only has one trait to subtract
behavior SubBehavior ('number: int):
trait Subtract (other: int):
'number -= other
# Create a character which combines the two behaviors
character Calculator ('number1: int, 'number2: int):
AddBehavior ('number1),
SubBehavior ('number2),
struct Bar:
a: int,
b: int,
function Main:
# Assign foo as 0 and appoint the Calculator character
foo := 0 [Calculator (', ')]
foo->Add(10)
# foo == 10
foo->Subtract(10)
# foo == 0
# Assign bar as a = 0 and b = 0 then appoint the character
# to each field.
bar := Bar (0, 0) [Calculator ('a, 'b)]
bar->Add(10)
# bar.a == 10
bar->Sub(20)
# bar.b == -20
# Take notice that this time it was b, not a

The behaviors don’t necessarily need to share the same data, as shown here. They can simply be grouped together for ease of use, or because of related functionality. That is the reason characters exist. They group together traits, behaviors, and even other characters.

In this case for functionality, this is also very similar to the first example until the bar section. This is just one possible interpretation and implementation of BP, and this could even be disallowed in a language through constraints or restrictions if the designer so wishes. This just shows another way to interpret appointment.

Further Constructs

Do you want interfaces? Go for it. Polymorphism between behaviors? Go for it! Overriding? Overloading? Any other feature? It is all up to you. BP will not hold any language designers back from implementing whatever they wish, especially if it innovates on or by using BP.

Ways Behavioral Programming Can be Implemented

In essence, BP should be implemented however language designers wish. There is no set rules or constructs besides traits and appointment. Designers can choose to not even implement behaviors or characters. BP is built to be open to interpretation and implementation. Nothing has to be implemented if it is not a good fit for the language, as long as you can create traits and appoint them. BP is meant to enhance languages through the paradigm’s unique design, ideology, and tools, not force them to fit a certain criteria of requirements to be “true” BP.

For type-checking and appointments, these could be either compile-time or runtime, whichever fits the language best. A designer could go the Pythonic route and add trait objects straight onto their data, similar to how Python handles class methods, or these can be checked at compile-time, potentially passed through means of macros, parametric polymorphism, or other generic designs. It is up to the language designer to choose the best interpretation and implementation of BP for their language.

Don’t use a hammer on a screw. Use the right tools for the job. BP is just another tool.

Relating Behavioral Programming

BP shares functionality with many different concepts, though the ideology is quite different. For example, JavaScript’s mixins allow for the same functionality as BP. Though due to the implementation, and the nature of dynamic languages, these are typically discouraged within JavaScript. BP mostly helps static languages, creating a design structure that allows for essentially compile-time mixins (and/or even runtime, depending on the design of the language, using something such as a vtable), though a dynamic language properly designed around the ideology of BP could still take advantage of its benefits. However, the ideology behind traits are much more nuanced than being a mix between methods and mixins.

Rather than just the functionality, it is the thought and design process of structuring the implementations in a loosely coupled manner to allow for code reuse and “injection”, as in allowing users to replace functionality of a behavior or character without altering the functionality of the other traits. This means that traits should be designed in such a manner that allows for that, unless it is absolutely not possible. However, the great thing about BP is that even if a developer changes some functionality in their code, it will not at all alter the functionality within the behavior itself.

Say you develop a library of some kind using BP. A developer wishes to use your library. However, they wish to re-implement some logic from your library, but your library requires a specific implementation to function internally. In BP, this is not an issue. The developer simply re-implements the functionality and appoints that in their code, then only their code has the new functionality. Everything else in your library can use the original implementation, while the user is using a different one in their code.

Or say you’re working in a team on an application. There’s a specific part of code that requires slightly different functionality. You can simply create a trait or behavior, then appoint that to the desired data within that block of code. The desired functionality is given without requiring extending a class and doing casts, setting up dependency injection, doing any sort of configurations, or conditional checks within the code, and all without touching the code itself, save for appointments, thus avoiding adding more complexity. The code of everyone else on the team stays the exact same, and they don’t have to worry about any changes you make in isolated parts of code.

Object-Oriented Programming and Behavioral Programming

As displayed above and mentioned at the beginning of the article, BP gives numerous benefits in refactoring, code reuse, debugging, reducing or removing most boilerplate code, unit testing, and immediate loose coupling of implementations, as well as their relation to the data. Let’s discuss each of benefits in further detail. Note that is not meant to bash OOP, but rather show what BP allows for when comparing to an already-established paradigm. OOP has some benefits over BP, but my purpose in this article is to teach BP, not compare every difference between BP and OOP. This works quite well though, as BP was initially designed to cover some faults in OOP, while coincidentally solving others issues that some developers have with it.

Refactoring in BP is much simpler than OOP. Since the data is loosely coupled from the implementation, the complexity of problems is lowered. Code reuse is a major contributing factor to this. Due to the appointment system, code can be reused easily while still allowing for flexibility in implementations. Traits can be used on anything that it is of the specified data type, and can made accessible only when appointed. Developers can choose whichever implementation they wish, reusing code in a modular manner from one or more sources. This means that, within a system that implements proper BP design philosophy, you can be absolutely certain that a change in a trait will not affect code it is not appointed in, while OOP’s encouraged inter-use of methods within other methods can lead to cascading effects inside of potentially unrelated code. This also feeds in well to the next benefit of BP.

Debugging can also be much simpler than in OOP. With BP, developers only need to alter appointments in specific areas of code if they wish to narrow down issues, or debug specific parts of code. This could allow the rest of the code to function as normal without any change, especially in speed, and only requires (most likely) a line or a few lines of code to change.

Boilerplate code can be a serious issue in OOP. But how does BP reduce this? Due to appointment, BP can reduce boilerplate code by combining different possible traits together, only having to code the parts that differ. The boilerplate isn’t removed, as it is almost impossible to do so without scenario-specific tools, but it can severely reduce it within most scenarios.

Unit testing can be done on individual traits due to their isolated nature. The traits, when the BP design philosophy is implemented properly, will be incredibly loosely coupled from the data, as well as from each other. Unit tests simply need to test each trait individually without any conflict.

For the final point, I will take a quote directly from Joe Armstrong, the creator of Erlang.

The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.

- Joe Armstrong

In BP, you specifically choose what traits you want. Don’t want specific functionality? Then don’t use them. It’s that simple. No one else’s code can alter your own without you wanting it to do so. It essentially puts each set of code into its own bubble, isolated from each other. Does something carry more than what you need? Just de-appoint it. BP gives you full control over what functionality you wish to use, unless constrained by the language itself.

Drawbacks of Behavioral Programming

Though it has many benefits, just like anything, BP has drawbacks. Without a proper system to template out appointments, it can become incredibly verbose. Only appointing traits necessary within a block of code leads to quite a lot of typing. It is important that any language implementing BP have some system to create appointment templates. HOWEVER, these templates should only be used when they are right for the job. If you appoint a lot of traits that are not necessary in a block of code, you still get the gorilla and the whole jungle, though you still retain control over what implementations you choose. Here is an example of how a templating system could potentially be achieved:

trait Add ('value: int, input: int) {
value += other;
}
# Define an appointment template for ints with Add
template AddableInt = int [Add(')]
function Main:
# Construct an Addable int
foo := AddableInt(10)
foo->Add(10)
# foo == 20

As shown here, the AddableInt appointment template allows the user to construct an integer with preset traits. This removes a lot of the verbosity with appointment, allowing users to gain access to at least basic traits without having to appoint them explicitly.

BP also struggles when a developer wishes to design their code (e.g. library, framework, etc.) to use user-defined appointments. In this case, something like a vtable, compile-time generics, or passing one or more function pointers is necessary. This can limit the design decisions a language can make, and force them down a specific path, as well as potentially causing some optimization issues with branch predictions. This is a shame, as this is a great point at where BP could shine.

BP may also potentially struggle with parallelization, particularly if the software depends on mutable state. BP does not solve this issue, but still offers ways to get around it, due to its incredible openness to interpretation. For example, this issue could be solved by using traits like individual compute units, and passing objects to them for computation before returning either an updated value(s) or returning an entirely different type of value(s). This could also be solved by using immutable states.

Why Create Behavioral Programming?

As I used Object-Oriented Programming more and more, I began to notice issues with it. These issues were the rigidness in its modern implementations, the tight coupling of data and implementation inherit to the paradigm, and even the tight coupling of implementation itself! Of course, these issues could be solved with carefully-designed libraries and systems or design patterns that often add complexity. However, most developers won’t take the time or even have the forethought to allow for such functionality. BP allows for this out of the box due to its design and without adding complexity to the program, while also solving other issues that some developers have with OOP. BP was inspired by Object-Oriented Programming, taking its best features, removing anything unnecessary, and allowing for so much more, but now BP has evolved into its own, substantial paradigm. Language designers can interpret BP however they wish, implementing their own unique takes on it. As long as you can create traits and appoint them, it’s BP.

Modularity without compromise and without restriction.

That is the Behavioral Programming Paradigm.

--

--