The overall goal here is to achieve loosely coupled components that can be developed with cross-cutting concerns that don’t influence each other. Expression Problem is one instance of this problem. The reason why we want this goal is because it’s an important stepping stone towards having Well Designed Abstractions
Compatibility here referrs to two components (“client” and “server” are common components where this matters). Ideally, the server can evolve separately from the client. Crucially, there may also be several clients of various versions running around, so you want to be maximally compatible when possible.
True backwards compatibility is really about avoiding your desired level of breakage when getting ready to release a client version that’s “fully” compatible. Likewise, forwards compatibility is about giving the server as much ability as possible to maintain backwards compatibility easily.
Achieving backwards compatibility is done by gracefully degrading in the presence of missing information.
Achieving forwards compatibility is done by gracefully preserving and ignoring extraneous information.
That is, one might think of open/extensible records, which is exactly what forwards compatibility is. Having the fields of the records all denoted as Maybe/Either values is what backwards compatibility is.
That said, there’s more to it than just explicitly handing missing information. It’s very important for forwards compatibility to preserve extraneous information if necessary.
Example: say you have a server sending information to a client and you insert a secondary client in the middle whose job is to capitalize titles in a payload object. The middle client will only be forwards compatible if it doesn’t mangle the object while capitalizing the titles; allowing for it to be a transparent abstraction.
While this is more about the structure of the data, actual evolution of the communication APIs generally take place using some form of Branch By Abstraction.