Implementation no longer defines both the public and local API, rather these are now dedicated protocols.
We also remove Self requirements from this interface, and instead use a constrained associated type within the Local and Public protocols as a type parameter to Message. Implementation now only need define a Payload (this allows us to remove the type projection to an enum state in a switch case to define a single payload that behaves differently – instead we now have a dedicated type).
Since this is a tremendous refactor in breadth, there aren't really any good opportunities to break it up into stages for commits (hence the single commit). If we commit changes in parts then they don't really make sense with the rest of the codebase (since we're transitioning to a new API), and so they aren't really very valuable. IMO a single commit is best because allows one to follow code to its new home (rather than hunting around across different commits).
These changes require dropping Swift 4.0 support in favour of Swift 4.1 and up – in the Public and Local protocols the mentioned constrained associated type which refers to the implementation also need implement the Public or Local protocol itself.
This allows us to have a subtype of the version write a base implementation, and have the version itself defer to it without needing to declare a payload itself. This is important because while we require a generalised implementation, we cannot have a version implement directly two separate base Public and Local implementations if these require a payload (because there is only one type name, but two payload types are required – one for the Local and one for the Public implementation).
Instead we separate the Local and Public API from their implementations Local & Implementation, Public & Implementation respectively. This also serves to ensure that if we would like to specify a message, we require an Implementation (e.g. Version2.Local and not just Version2 – even though Version2 implements Local, a version2 message is not specific enough). On the contrary, if we only wish to use the API of local we need not refer to Version2.Local.encrypt for example, rather we can instead use just Version2.encrypt since this method name is enough to defer us to the Local base implementation.
For sanity, we also require that any Local or Public defer to a base implementation by constraining our deferred implementation's deferred implementation to be identical to our deferred implementation (this ensures that deferral chains are at most of length 2, and are importantly not recursive (unless they are of length 1) which means will will always "settle" on a true base implementation).
Implementation
no longer defines both the public and local API, rather these are now dedicated protocols. We also removeSelf
requirements from this interface, and instead use a constrained associated type within theLocal
andPublic
protocols as a type parameter toMessage
.Implementation
now only need define aPayload
(this allows us to remove the type projection to an enum state in a switch case to define a single payload that behaves differently – instead we now have a dedicated type).Since this is a tremendous refactor in breadth, there aren't really any good opportunities to break it up into stages for commits (hence the single commit). If we commit changes in parts then they don't really make sense with the rest of the codebase (since we're transitioning to a new API), and so they aren't really very valuable. IMO a single commit is best because allows one to follow code to its new home (rather than hunting around across different commits).
These changes require dropping Swift 4.0 support in favour of Swift 4.1 and up – in the
Public
andLocal
protocols the mentioned constrained associated type which refers to the implementation also need implement thePublic
orLocal
protocol itself. This allows us to have a subtype of the version write a base implementation, and have the version itself defer to it without needing to declare a payload itself. This is important because while we require a generalised implementation, we cannot have a version implement directly two separate basePublic
andLocal
implementations if these require a payload (because there is only one type name, but two payload types are required – one for theLocal
and one for thePublic
implementation).Instead we separate the
Local
andPublic
API from their implementationsLocal & Implementation
,Public & Implementation
respectively. This also serves to ensure that if we would like to specify a message, we require anImplementation
(e.g.Version2.Local
and not justVersion2
– even thoughVersion2
implementsLocal
, a version2 message is not specific enough). On the contrary, if we only wish to use the API of local we need not refer toVersion2.Local.encrypt
for example, rather we can instead use justVersion2.encrypt
since this method name is enough to defer us to theLocal
base implementation.For sanity, we also require that any
Local
orPublic
defer to a base implementation by constraining our deferred implementation's deferred implementation to be identical to our deferred implementation (this ensures that deferral chains are at most of length 2, and are importantly not recursive (unless they are of length 1) which means will will always "settle" on a true base implementation).