[swift-evolution] Proposal: 'selfless' keyword for refactoring duplicate code from initialisers

ilya ilya.nikokoshev at gmail.com
Wed Dec 16 02:13:52 CST 2015


You can do


init(<initial arguments>)
{
    color = initialiseColor(<subset of arguments>)
    size = initialiseSize(<subset of arguments>)
    font = initialiseFont(<subset of arguments>)
}

On Wed, Dec 16, 2015 at 04:36 Ross O'Brien via swift-evolution <
swift-evolution at swift.org> wrote:

> Lazy properties can't make use of parameters of the init() without storing
> them, and can't calculate parameters to be used in a super.init() (or
> decide which super.init() is called, if there are several to choose from).
> Part of the idea of the selfless function is to keep local lets/vars at
> local scope rather than type scope, and allow functions which perform
> calculations in an initialisation to be reusable.
> Static functions can perform these calculations but can't set instance
> properties.
> At the moment, for an init() to use initial parameters without storing
> them, all the code for setting properties has to be in the init; I don't
> think there's a way to break an init up into a sequence of smaller function
> calls, each dealing with a lower level of abstraction of the init. i.e.
> something like this:
>
> init(<initial arguments>)
> {
>     initialiseColor(<subset of arguments>)
>     initialiseSize(<subset of arguments>)
>     initialiseFont(<subset of arguments>)
> }
>
>
> On Wed, Dec 16, 2015 at 1:06 AM, Marc Knaup <marc at knaup.koeln> wrote:
>
>> In its current state with the initial example -1 from me for the proposal.
>>
>> The example could easily be written like this:
>>
>> class FooView: UIView {
>>
>>     var property = 4
>>
>>     init() {
>>         super.init()
>>     }
>>
>>     init(frame: CGRect) {
>>         super.init(frame)
>>     }
>> }
>>
>>
>> In cases where the initially value is computed in a complex way a closure
>> can be used:
>>
>> class FooView: UIView {
>>
>>     var property: Int = {
>>         // some complicated computation
>>         return value
>>     }()
>>
>>     init() {
>>         super.init()
>>     }
>>
>>     init(frame: CGRect) {
>>         super.init(frame)
>>     }
>> }
>>
>> And that value could even delegate computation to static methods of the
>> class.
>>
>> In cases where the computation is even more complex and refers to other
>> properties a lazy var can be used, which even allows the value to refer
>> to self:
>>
>> class FooView: UIView {
>>
>>     private(set) lazy var property: Int = {
>>         // some complicated computation which can use self
>>         return value
>>     }()
>>
>>     init() {
>>         super.init()
>>     }
>>
>>     init(frame: CGRect) {
>>         super.init(frame)
>>     }
>> }
>>
>>
>>
>> On Wed, Dec 16, 2015 at 1:30 AM, Charles Srstka via swift-evolution <
>> swift-evolution at swift.org> wrote:
>>
>>> On Dec 15, 2015, at 5:59 PM, Ross O'Brien via swift-evolution <
>>> swift-evolution at swift.org> wrote:
>>>
>>>
>>> Hi all,
>>>
>>> I'm a new member of the list, so apologies if this is a duplicate of an
>>> existing idea or if there's already a way to do this in Swift 2.1 that I've
>>> missed.
>>>
>>> In Objective C, and C-like languages, an initialiser function represents
>>> a stage after allocation of memory where properties are given values. In
>>> Swift, init appears to precede (or overlap with) allocation. The benefit of
>>> this is that for type-safety reasons, all properties of a type (or new
>>> properties of a derived type) can be verified as having values. The
>>> disadvantage, and one of the stumbling blocks for those who learned
>>> Objective-C, is that until all the properties have values, the instance
>>> does not exist and instance functions cannot be called.
>>>
>>> There's an invisible threshold in Swift init() functions marking this
>>> transition. In derived classes it's the point where super.init() is called
>>> - after the derived type has provided initial values, but before any type
>>> functions can be called.
>>>
>>> Some types have multiple initialisers, and may be duplicating a lot of
>>> code in those distinct inits before they cross the threshold. This code
>>> can't be refactored into an instance function because the instance doesn't
>>> exist yet. The instance function may not even require the use of any
>>> properties of the type.
>>>
>>> If the compiler can read an init function and its varied control flow
>>> and determine a threshold where all properties have values, presumably it
>>> can read the code of any function called before that threshold, determine
>>> which properties they read and which they assign to, and provide a warning
>>> if a path assigns to a constant a second time, etc.. But this isn't
>>> currently happening.
>>>
>>> I'm guessing there are multiple contributing factors for this: the
>>> combinatorial explosion of possible control flow paths with functions
>>> (particularly if they're recursive); the possibility that the function
>>> calls are used by the compiler to mark the end of a control flow path, by
>>> which point it can determine whether everything has a value; the function
>>> genuinely can't exist without allocation. I don't know the reasons but I'd
>>> be interested to learn them.
>>>
>>> I'm proposing the keyword 'selfless' for a function which could be
>>> called before the threshold. It either only uses local properties or
>>> properties belonging to the type - never to the 'super' type (in the case
>>> of a derived class). It can't call any instance functions which aren't
>>> themselves selfless.
>>>
>>> Example of use:
>>> class FooView : UIView
>>> {
>>>     var property : Int
>>>
>>>     init()
>>>     {
>>>         initialiseProperty()
>>>         super.init()
>>>     }
>>>
>>>     init(frame:CGRect)
>>>     {
>>>         initialiseProperty()
>>>         super.init(frame)
>>>     }
>>>
>>>     selfless func initialiseProperty()
>>>     {
>>>         property = 4
>>>     }
>>> }
>>>
>>> Is this something of interest?
>>>
>>> Regards,
>>> Ross O'Brien
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>
>>>
>>> +1. This is something that I was planning to propose. It comes up
>>> particularly often in Cocoa objects that implement NSCoding, where you have
>>> to implement both init(coder:) and the designated initializer. Currently,
>>> if you have a bunch of complicated code involved in setting defaults for
>>> your properties, in a manner that’s too complex to solve with simple
>>> default values, you end up with a lot of copy-paste code in the two
>>> initializers, which can easily get out of sync if one is edited without
>>> being diligent about editing the other one in the same way. The exception,
>>> of course, if if you make init(coder:) a convenience initializer, but then
>>> subclasses cannot call super’s implementation of init(coder:), which makes
>>> this unworkable in a lot of circumstances.
>>>
>>> I’m not sure “selfless” is the right keyword for this, but some sort of
>>> feature along these lines would be incredibly helpful.
>>>
>>> Charles
>>>
>>>
>>> _______________________________________________
>>> 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/20151216/f9204647/attachment.html>


More information about the swift-evolution mailing list