Expression Problem.
The Expression Problem is a software engineering dilema where the more implementations an abstract data type has, the harder it is to add new behaviors, and vice versa. Take a shape, for instance. If a program has distinct implmentations for a circle, { center, radius }
, and a square, { center, dimension }
, then any new function which operates on shapes must implement a custom implementation for each of circle and square, as the state of each have no overlap in design. A function which detects if a point is inside or outside a shape would need to check euclidean distance to the center of a circle, but check taxi cab distance to the center of a square. Each combination of implementation $I$ and behavior $B$ requires custom code, $C = I\times B$. There is no solution to the expression problem, and so the only way to mitigate the combinatorial explosion of code complexity is to either be conservative with implementations, or with behavior, though introducing multiple levels of abstraction can adjust the equation.
Encapsulated vs Exhibited.
Object Oriented Programming follows the convention of encapsulation of state. State may only be accessed by select methods which are listed on the abstract data type. Any new implementation is also required to define its own implementations of these behaviors, though they are allowed to adopt defaults when applicable. Extending an object with new methods creates a requirement that a new implementation of that method be added to every single concrete object which implements that abstract type, which may be impossible if any of the concrete types are outside of the programmers control. Adding a new concrete implementation of an abstract type, conversely, may always be performed, even if it may require all new methods. The conventions and design of OOP languages are engineered toward facilitating new permutations of state to satisfy data types, but not new behaviors of those data types.
Functional Programming on the other hand, follows the convention of exhibition of state. Data types are represented by primitive values, such that the state therein may be access by any function with access to the data type. New behaviors of that data type are then as simple as writing a new function of that value which implements the behavior. A programmer is never restricted from making these new functions, even if the creation of the values is outside the programmers control. Creating a new variant of the state for the same data type, conversely, may be very difficult, as every function throughout the system that expects the value to exist in its current form would need to be edited to have two distinct, branching code paths. This might not be possible if some of the functions are outside of the programmers control. The conventions and design of FP languages are engineered toward facilitating new functions on existing data types, but not new permutations of state on existing data types.