Behaviours in Elixir (at least) are about contracts. In this case, about making an _explicit_contract for others to implement. This idea (of contracts) is transversal to any programing paradigm you may use, be it OO, FP LP or something even crazier.
The idea here is that the rules of a good Architecture, which are based on defining boundaries and contracts are paradigm independent. Thus, it follows that Elixir/Erlang would need some way to define contracts - which came in the form of behaviours.
You spec the public API explicit so that other modules can implement it.
Doing it this way allows you later to move between implementations easier, decouple your code and test it.
When using Bheaviours/contracts don’t think about functions. Think about public API that other clients will use.
Behaviours probably resulted out of the way genservers are implemented. It’s a process, which does handle a lot of otp behaviour for you, but to be useful it needs to also do some user defined stuff. To be able to do that you give the genserver a module at startup, which the genserver calls, when userland code is required. Now when implementing said module you need to know which functions will be called by the genserver code and that’s what a behaviour defines.
You don’t need behaviours just as you don’t need salt in your food.
It is still going to be nutritious, but unless your doctor forbids you, using salt will make it a lot better.
Contracts/Behaviours are the same. I coded JS for several years without interface / behaviours, but I always defined my contracts and I always made sure my team followed the contracts.
This allowed us to develop code against contracts (even when the implementations were not ready) and to test it better.
So yeah, it’s up to you. Do you like salt in your food?
Anyway Behaviours are similar to Interfaces in OOP language. You don’t need interfaces, many OOP languages don’t have interfaces. To be hones I never wrote any by myself. But if you are using interface, then you know that this given class (or module in Elixir) is interchangeable with every other class that uses this interface as long as you use a function that this interface guarantees. And it is very nice guarantee to have. Then you can safely change one class/module for other. For example, if you have few HTTP clients, and all of them use same interface/behaviour you can safely change between them without rewriting large portions of code.
From the OO perspective behaviours need to be understood in terms of the intent of the Template Method pattern:
Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure. (GoF)
i.e.
Define the skeleton of a process, deferring some functionality to the callback module.
In Template Method the AbstractClass does expose an interface (implying a contract) that will be common to the ConcreteClasses but AbstractClass also contributes the common behaviour (which an interface cannot). In some ways the Template Method pattern is a compile time version of the Strategy pattern:
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it. (GoF)
i.e.
Define a family of processes (workers) which can be uniformly supervised and managed. Callback modules lets the process computation vary independently from how it is supervised and managed.
Similarly a behaviour module is in charge of most of the standardized (common) aspects of the process lifecycle while the callback module primarily deals with the specific computation (work) that the process is responsible for.
Behaviours are module based and are sometimes used at compile time to vary production and testing module implementations but that is a degenerate use case.
During runtime (process instantiation) a process composes the behaviour and callback module - i.e. it’s only through processes that a behaviour module can spawn “multiple instances” of the behaviour, each process manifesting a specific binding between a behaviour and a callback module with its own state.
Designing for Scalability with Erlang/OTP p.10:
OTP generic behaviors can be seen as formalizations of concurrent design patterns.
So from the OTP perspective behaviours are about processes, not FP.
Actually behaviours predate Elixir by about 15 years. Elixir has inherited them from Erlang/OTP and uses some the standard OTP behaviours, like gen_servers, supervisors and applications, and extended them by adding some new behaviours, agent and task. Doing this is nothing strange as the behaviour concept is easily extensible both by design and implementation.