[swift-evolution] Proposal: Allow class cluster pattern via dynamic initializer return type
ilya
ilya.nikokoshev at gmail.com
Mon Dec 7 16:25:32 CST 2015
Yes, that's what I suggest:
*required initializers*: must call super (autogenerated for base class)
*convenience initializers*: must assign to self via another initializer
(including via subclass)
(with the minor technical correction that convenience initializers, as is
now, are able to call other convenience initializers)
Although note that inheritance rules for "class cluster" initializers will
be very different, the reason being that regular convenience init is
essentially
init(x...) -> Self {
self = Self(y: ...)
}
which is covariant enough to be able to be inherited, while "class cluster
init" is
init (x...) -> BaseClass {
self = SomeConcreteClass(y: ...)
}
which is not covariant.
On Tue, Dec 8, 2015 at 1:06 AM, Riley Testut <rileytestut at gmail.com> wrote:
> So instead of chaining convenience initializers to required ones via
> self.init(), you’d recommend instead assigning them directly to self? I
> think this would be a nice change; currently it can be hard to know the
> correct method of chaining initializers (at least as far as I’ve seen when
> attempting to explain it to newcomers).
>
> Proposed new rules for initialization:
>
> required initializers: must call super (unless base class)
> convenience initializers: must assign to self via a required initializer
>
> I think this also would help with the confusion of why convenience methods
> can’t call super, but required ones can, since now convenience methods
> can’t chain to *any* initializers directly. Thoughts from others?
>
> On Dec 7, 2015, at 1:17 PM, ilya <ilya.nikokoshev at gmail.com> wrote:
>
> more precisely like this for regular convenience initializers
>
> class SomeClass {
>
> convenience init(x: Int) {
> let y = someComputation(x) // <- can't use self here
> self = init(y: y)
> self.configure() // <- can use self here
> }
>
> init(y:Int) { // designated
> ...
> }
> }
>
> On Tue, Dec 8, 2015 at 12:14 AM, ilya <ilya.nikokoshev at gmail.com> wrote:
>
>> Actually I think self = SomeClass(anotherInit: ...) would be a better
>> syntax of choice for *all* convenience initializers, as it would make
>> intuitively clear that self cannot be used until this call.
>>
>> On Tue, Dec 8, 2015 at 12:11 AM, Riley Testut <rileytestut at gmail.com>
>> wrote:
>>
>>> I actually really like the idea of using a convenience initializer for
>>> this, it works well with the Swift initiation process. +1 (though maybe a
>>> different keyword than "convenience" to show that it actually assigns to
>>> self?)
>>>
>>> On Dec 7, 2015, at 1:07 PM, ilya <ilya.nikokoshev at gmail.com> wrote:
>>>
>>> I actually like the way it's done in the Objective-C, where _isa pointer
>>> will be changed appropriately.
>>>
>>> Perhaps it's actually possible to solve this problem:
>>>
>>> > Unfortunately, this is wasteful; memory is allocated for the base
>>> class, and then subsequently replaced with new memory allocated for the
>>> appropriate base class. More importantly though, the whole process can be
>>> complicated;
>>>
>>> by noting that we can formally mark this initializer as convenience
>>> initializer, and memory allocation doesn't need to happen until we hit a
>>> designated initializer. So far I see no reason why this cannot be made to
>>> work:
>>>
>>> class Cluster {
>>>
>>> convenience init(parameters) {
>>> if stuff {
>>> self = _Cluster1(...)
>>> } else {
>>> self = _Cluster2(...)
>>> }
>>>
>>> // it's safe to continue with second init phase
>>> self.configure(...)
>>> }
>>> }
>>>
>>> class _Cluster1 {
>>> init(parameters: ...) { ... } // designated init, guaranteed never
>>> to call convenience inits
>>> }
>>>
>>>
>>> On Mon, Dec 7, 2015 at 11:55 PM, David Owens II via swift-evolution <
>>> swift-evolution at swift.org> wrote:
>>>
>>>> Well, the basic idea of a class cluster is to hide the internal
>>>> implementation. The caller of the API is still supposed to get back an
>>>> instance of the clustered type (or that conforms to the interface at least).
>>>>
>>>> It’s still possible to create class clusters though; here’s an example
>>>> playground:
>>>>
>>>> private protocol _Cluster {
>>>> func description() -> String
>>>> }
>>>>
>>>> class Cluster {
>>>>
>>>> private var _instance: _Cluster
>>>>
>>>> init(name: String) {
>>>> _instance = _ClusterString(name: name)
>>>> }
>>>>
>>>> init(value: Int) {
>>>> _instance = _ClusterValue(value: value)
>>>> }
>>>>
>>>> func description() -> String {
>>>> return _instance.description()
>>>> }
>>>> }
>>>>
>>>> private class _ClusterString: _Cluster {
>>>> private var name: String
>>>> init(name: String) { self.name = name }
>>>> func description() -> String {
>>>> return "_ClusterString: \(name)"
>>>> }
>>>> }
>>>>
>>>> private class _ClusterValue: _Cluster {
>>>> private var value: Int
>>>> init(value: Int) { self.value = value }
>>>> func description() -> String {
>>>> return "_ClusterValue: \(value)"
>>>> }
>>>> }
>>>>
>>>> let s = Cluster(name: "a string")
>>>> s.description()
>>>>
>>>> let v = Cluster(value: 12)
>>>> v.description()
>>>>
>>>>
>>>>
>>>> The implementation is different from how ObjC implements class
>>>> clusters, but the end result is nearly identical in functionality. If Swift
>>>> had a form of function redirection, this pattern could be supported with
>>>> less boiler-plate. However, I don’t believe this proposal is necessary to
>>>> support class clusters.
>>>>
>>>> -David
>>>>
>>>>
>>>> On Dec 7, 2015, at 12:19 PM, Riley Testut via swift-evolution <
>>>> swift-evolution at swift.org> wrote:
>>>>
>>>> Happy Monday everyone!
>>>>
>>>> I wrote up a prototype proposal, which is probably best viewed on
>>>> GitHub (
>>>> https://github.com/rileytestut/swift-proposals/blob/master/class-cluster.md).
>>>> But for convenience, I’ve included it in this email body as well. Hopefully
>>>> someone else thinks this would be an idea worth considering :-)
>>>>
>>>> *## Introduction*
>>>>
>>>> Throughout its frameworks, Apple makes use of the “class cluster”
>>>> pattern as a means to separate the public API out from the (potentially
>>>> complex) internal representations of the data. Clients of the API simply
>>>> use the public API, while under the hood a different implementation is
>>>> chosen to most efficiently represent the provided initialization parameter
>>>> values.
>>>>
>>>> Unfortunately, because initializers in Swift are not methods like in
>>>> Objective-C, there is no way to specify what the actual return value should
>>>> be (short of returning nil for failable initializers). This makes it
>>>> *impossible* to actually implement the class cluster pattern in Swift.
>>>>
>>>> *## Motivation*
>>>>
>>>> While developing my own Swift framework, I found myself wanting to
>>>> provide a similar functionality. For the client of said framework, I wanted
>>>> them to be able to create an instance of an essentially abstract class, and
>>>> be returned a private subclass instance suited best for handling whatever
>>>> input initialization parameters they provided. It didn’t make sense given
>>>> the circumstances to ask the user to decide which class would be the best
>>>> representation of the data; it should “just work”.
>>>>
>>>> Additionally, the class cluster pattern can make backwards
>>>> compatibility significantly easier; instead of littering your code with
>>>> branches for different versions of an OS, you could instead have one
>>>> if/switch statement to determine the appropriate subclass for the current
>>>> OS you’re running on. This allows the developer to trivially keep legacy
>>>> code for older platforms while taking advantage of new APIs/designs, and
>>>> also without changing *any* client code. An example of the class cluster
>>>> pattern being used for this reason can be seen here:
>>>> http://www.xs-labs.com/en/blog/2013/06/18/ios7-new-ui-strategies/
>>>>
>>>> *## Proposed solution*
>>>>
>>>> I propose that we allow for implementation of the class cluster pattern
>>>> by providing a way to (at run time) specify the actual type that should be
>>>> initialized depending on the provided initialization parameters.
>>>>
>>>> *## Detailed design*
>>>>
>>>> *Introduce a new class method that can return an appropriate type that
>>>> should be used for initialization, depending on the provided initialization
>>>> parameters.*
>>>>
>>>> This is what I believe to be the most clean solution, and with
>>>> (assumedly) minimal impact on the existing nature of Swift’s initialization
>>>> process. To ensure this remains safe, the only types allowed to be returned
>>>> should be subclasses of the parent class (such as returning a __NSArrayI
>>>> for NSArray). Notably, beyond this method, everything else remains the
>>>> same; all this does is change what class the initializer is called on
>>>> initially.
>>>>
>>>> Here is an ideal implementation gist:
>>>> https://gist.github.com/rileytestut/0e6e80d3f22b845502e7
>>>>
>>>> *## Impact on existing code*
>>>>
>>>> There will be zero impact on existing code; if the proposed class
>>>> method is not implemented, then it will default to simply initializing the
>>>> “base” class, as it always has.
>>>>
>>>> *## Alternatives considered*
>>>>
>>>> *Allow for return values in initializers*
>>>>
>>>> This is essentially how most class cluster patterns are implemented in
>>>> Objective-C. Inside the init method, the class inspects the provided
>>>> parameters, then assigns self to an instance of the appropriate subclass.
>>>> Unfortunately, this is wasteful; memory is allocated for the base class,
>>>> and then subsequently replaced with new memory allocated for the
>>>> appropriate base class. More importantly though, the whole process can be
>>>> complicated; it can be very easy to make an infinite recursive loop by
>>>> calling [super init] in the subclass, which then assigns self to a new
>>>> instance of the subclass, which then calls [super init]…etc.
>>>>
>>>> tl;dr; this method would work, but would be somewhat inconvenient to
>>>> implement.
>>>>
>>>> *Class function to return appropriate instance*
>>>>
>>>> This is probably the simplest approach: simply make a class function
>>>> that returns an instance of the appropriate class given a few input
>>>> parameters. This totally works, but it means consumers of the API have to
>>>> remember to use the class method instead of the initializer. Even if all
>>>> initializers for the class were marked private, it would be strange to have
>>>> the dissonance between using initializers and class methods to instantiate
>>>> types in code. The consumer should not have to know about *any* of the
>>>> implementation details; everything should “just work”. Forcing them to use
>>>> alternative means to instantiate objects breaks this philosophy, IMO.
>>>>
>>>> *Derive from Objective-C base class*
>>>>
>>>> Another option is to simply derive from an Objective-C base class, and
>>>> this is actually what I am doing right now in my framework. Unfortunately,
>>>> there is one significant drawback: because the initialization is happening
>>>> in Objective-C, you can only provide Objective-C compatible types for the
>>>> initialization parameters (so no Swift structs for you!). Additionally,
>>>> this (obviously) means whatever code is using it is limited to systems with
>>>> Objective-C support, so it is not as portable as a pure-Swift solution.
>>>>
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>>
>>>>
>>>>
>>>> _______________________________________________
>>>> swift-evolution mailing list
>>>> swift-evolution at swift.org
>>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>
>>>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151208/ec7af26c/attachment.html>
More information about the swift-evolution
mailing list