Compositional Initialization

Ah, sorry, I missed that distinction; I was arguing the wrong argument, it seems. Whoops!

So what you’re saying is that an explicit initialiser could still ignore Optionals re. initialising them, but that synthesised ones would not - specifically that they would not have an “= nil” default on the corresponding init parameters?

Yeah. I don't want to get too off-topic here but making the memberwise initialiser parameter list match the member variable declarations would do that, and you could easily opt back in by explicitly setting the member variable to nil.

1 Like

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 Partials 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 Partials. 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 WritableKeyPaths 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.

I agree 100%. I'm updating the proposal now to reflect this, I like <- a lot better. Thank you

Updated version is now on the repo, and will be in the final proposal. I'd be happy to add you to the authors if you like.

Hah, I’m flattered, but don’t think I’ve earned that. Always happy to help though, even if only in small ways.

I thought we had that; I'm half-remembering that some post somewhere mentioned that only the "?" had the default nil semantic.

struct MyStruct {
    var myOptional1: Int?  // Defaults to `nil`
    var myOptional2: Optional<Int>  // Must provide value in designated initializers
}

Or am I misremembering here?

2 Likes

It's a little magical, but yes, that is the status quo.

That’s true! Then that would solve the problem, right?

As an update, I have not forgotten about this pitch, but I started a new job and was swamped for awhile.

But recently when looking into dependency injection frameworks, I came back around to wanting something like this pitch.

However I realized it would be best to make a separate pitch the idea of making keypaths usable for initializing stored properties at init time, since that's the only aspect of this proposal that would require a major change to how Swift currently works. That pitch is now in a separate thread.

If there are no objections to adding support for using keypaths to initialize properties then I will make a proposal for that, and then this pitch could be a follow-up, or maybe just a third-party open source add-on package etc.