That's very nice, and I like how you incidentally used the same verbiage from my proposal's proof of concept (e.g. the word "apply"/"applied" for the concept of applying the encapsulated changes to an instance).
Now with Array<PartialProperty>
, we get things like Equatable
for free. But I can see how with a bit more code, your Partial
could also support Equatable
, Hashable
etc. So that's great.
Another notable thing here is that by using a dictionary to store the values for the keypaths, you have avoided the issue of having multiple of the same property in a partial, something that the Array<PartialProperty>
in my pitch proposal can't do. (Now, whether or not that's desirable is another question, but I can see more arguments in favor than against, especially since not like you couldn't just have an array of Partial
s if you wanted, if for some reason you really wanted to be able to have some changes override other changes by order of precedence in the sequence.)
I also like how you've leveraged Swift 5's dynamicMember
here. When I originally wrote my proposal, dynamicMember
was not a thing yet, but it definitely expands what can be done here. You've shown how it might be leveraged to enrich the concept.
Now, let me ask you this, @mpangburn :
Lets say we were to update this proposal to incorporate the Partial
type. To do this, PropertyInitializable
protocol would need to be updated to work with Partial
s. To do that, we would need the following:
1. A failable initializer that accepts a Partial
and returns an instance only if the Partial
contains values for all the required properties of the type.
(in current Swift 5, that means, any var
that lacks a default value; ideally, we could also use a WritableKeyPath
to write a let
that has no default value within the init
, but as far as I'm aware that's impossible right now, please correct me if I'm wrong).
2. A way to handle initializing private(set)
variables that lack default values.
In the current working proof-of-concept implementation, we can do this:
final class Foo {
private(set) var bar: NSNumber?
private(set) var baz: URLSession? = nil
}
extension Foo: PropertyInitializable {
/// Required by the protocol, considered a "hack" until
/// such time as WritableKeyPaths might be usable on uninitialized values.
internal static var _blank: Foo = Foo()
}
/// Not using any sugar like to shorten the Property creation here.
///
/// Note the force cast here, since `bar` is `private(set)`.
var fooProps: [PartialProperty<Foo>] = [
Property(key: \Foo.bar as! WritableKeyPath, value: 5).partial
]
let foo = Foo(fooProps)
assert(foo != nil) // Will pass, only because `baz` has a default value.
Here, we can only make a Property
for initializing Foo.bar
only if we force-cast the keypath \Foo.bar as! WritableKeyPath
.
Even though the variable is declared as private(set)
, we can still set it using a coerced WritableKeyPath
because the init method which actually does the writing is internal to the type itself (because it adopted the protocol PropertyInitializable
).
That's not a bug, because why shouldn't a type be able to set its own variables using a WritableKeyPath
s supplied to it from elsewhere? (This method does not work for setting a let
at init time, but that's another issue.)
Summary of this point (2.): we need to figure out whether Partial
can be made to support this kind of forced cast to WritableKeyPath
, and if not, whether it can be updated to support that (and how).
I keep coming back to the issue of whether we can get some form of a KeyPath
—like an InitializableKeyPath
—that can only be used to write a value once, and only at init time, by the init method on the type (even for a let
with no default value). Hopefully, someone more familiar with the compiler and runtime can answer whether that's feasible or not.
Edit: One thing I'd like to add is that I see no reason why there couldn't be Partial
coexisting alongside the existing types in the proposal... can you? I still think there is some flexibility and power in being able to encapsulate a single Property<Root,Value>
as its own type, allowing for such things as the Mock
types in the example playground, where the generators for a value can be built into the property.
For such a use, the existing proposed types might make more sense than your proposed Partial
, not to take anything away from your idea. I'm just trying to suss out whether to replace the types in the existing proposal with Partial
or add Partial
as another aspect, if that's your intention and desire of course.
Maybe we can also add a PartialProtocol
to add more abstraction and polymorphism potential. That is the kind of flexibility I was going for with PropertyProtocol
, PartialPropertyProtocol
, etc.