Customized Inline Init Closure


(Weston Catron) #1

Ability to write an initializer while initializing an object.

Example

let name = “John Apple”;
let person = Person {
    self.name = nameInput.first() + " " + nameInput.last()
    self.dob = dateInput.datetime()
    If (self.age() > 18) {
        self.taxableStatus = INDEPENDANT
    } else {
        self.taxableStatus = DEPENDANT
    }
};

Helpful examples: Objects with many required parameters that are defaulted in the initializers.

SKLabelNode

let label = SKLabelNode(text: "Example")
label.position = CGPoint(x: 0, y: 250);
label.fontSize = 34;
label.fontColor = UIColor.blackColor()
self.addChild(label);

Can become:

let label = SKLabelNode(text: self.package!.title) {
    self.position = CGPoint(x: 0, y: 250)
    self.fontSize = 34
    self.fontColor = UIColor.blackColor()
}
self.addChild(label)

Readability Instead of a large amount of code setting up temporary variables to pass into an initializer, all initializing code could be wrapped in a closure.

Flexibility Instead of exhaustive initializers covering many use cases. Simpler initializers can be extended as needed. This can also encourage required properties over optional ones that are immediately defined.

Compiler Warnings Closures react the same as initializers within classes, warning users of incomplete implementation of required properties.

Possible disadvantages:

Sloppy Coding Instead of writing complete initializers programmers can just rely on in-line initializers.

Tried Before I found this feature is also available in C# (https://msdn.microsoft.com/en-us/library/bb397680.aspx). Not sure if it was helpful then but many languages since don't appear to use it.

-Weston


(Tino) #2

Something similar has been discussed ("method cascading"), and I hope the feature will be considered for Swift 4(?).
It is a little bit different, though:
"self" would not change its meaning (which imho is a plus), and the syntax is different.

Tino


(Adriano Ferreira) #3

Hey there!

As a suggestion, check out this simple yet very interesting project called Then <https://github.com/devxoul/Then> by @devxoul.

Best,

— A

···

On Jan 3, 2016, at 1:37 AM, Weston Catron via swift-evolution <swift-evolution@swift.org> wrote:

Ability to write an initializer while initializing an object.

Example

let name = “John Apple”;
let person = Person {
    self.name = nameInput.first() + " " + nameInput.last()
    self.dob = dateInput.datetime()
    If (self.age() > 18) {
        self.taxableStatus = INDEPENDANT
    } else {
        self.taxableStatus = DEPENDANT
    }
};

Helpful examples: Objects with many required parameters that are defaulted in the initializers.

SKLabelNode

let label = SKLabelNode(text: "Example")
label.position = CGPoint(x: 0, y: 250);
label.fontSize = 34;
label.fontColor = UIColor.blackColor()
self.addChild(label);

Can become:

let label = SKLabelNode(text: self.package!.title) {
    self.position = CGPoint(x: 0, y: 250)
    self.fontSize = 34
    self.fontColor = UIColor.blackColor()
}
self.addChild(label)

Readability Instead of a large amount of code setting up temporary variables to pass into an initializer, all initializing code could be wrapped in a closure.

Flexibility Instead of exhaustive initializers covering many use cases. Simpler initializers can be extended as needed. This can also encourage required properties over optional ones that are immediately defined.

Compiler Warnings Closures react the same as initializers within classes, warning users of incomplete implementation of required properties.

Possible disadvantages:

Sloppy Coding Instead of writing complete initializers programmers can just rely on in-line initializers.

Tried Before I found this feature is also available in C# (https://msdn.microsoft.com/en-us/library/bb397680.aspx). Not sure if it was helpful then but many languages since don't appear to use it.

-Weston

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Weston Catron) #4

I believe you’re referring to this https://gist.github.com/erica/eb32feb22ba99629285a currently being developed. I was initially limiting this proposal to initializers, specifically overriding them verses immediately following them. This is an important distinction because it would require required properties, verses the initializers still taking care of this. This doesn’t make it in opposition to method cascading, it might possibly be a nice addition. It also appears like self is not maintained (in that method cascading proposal anyhow), self is just not required unless variables names overlap. Below is my exercise in keeping self.

Perhaps this alternative would work:

class Foo {
    var name:String
    
    init (name:String) {
        self.name = name
    }
}

class Fee {
    var name:String = "Fee-fi"
    static func createFoo (name:String) -> Foo {
        let foo = Foo {
            foo.name = self.name + "-" + name
        }
        return foo;
    }
}

let name = "fo-fum"

let other = Fee.createFoo(name);
print(other.name) // "Fee-fi-fo-fum"

let another = Foo {
    // name = self.name // Ambiguous, self isn't defined.
    // self.name = name // Simplist, but isn't clear within scopes that contain self (like class functions) It would require a common javascript work around for this, saving a `that = this;` before the closure.
    another.name = name // Clear, another is only required when names overlap in scope.
    // print((self == nil)) // true
}

This has restrictions. An initializer can’t be used directly within a return for example:

class Fee {
    var name:String = "Fee-fi"
    static func createFoo (name:String) -> Foo {
        return Foo {
            return.name = self.name + "-" + name
        }
    }
}

That is admittedly awkward, bad form, but not technically wrong.

Unless there is a better alternative, keeping self the same inside the closure as outside seems difficult to do, but not impossible.

-Weston

···

On Jan 3, 2016, at 7:39 AM, Tino Heth <2th@gmx.de> wrote:

Something similar has been discussed ("method cascading"), and I hope the feature will be considered for Swift 4(?).
It is a little bit different, though:
"self" would not change its meaning (which imho is a plus), and the syntax is different.

Tino


(Weston Catron) #5

Thanks! Then looks very interesting and I’ll reach out to the lead there and see if they’re interested in working together. It definitely highlights similar readability issues I had outlined. I’m curious if there are compiler or other advantages to initializing more properties before continuing in the scope. For example race conditions, mulitthread support, concurrency... If you want to initialize more of an object than any single initializer allows being able to create it and set all desired properties before the system denotes it as officially initialized might be helpful. This would make a distinction between the init then { // do something} model verses init with { // do this to create it }.

-Weston

···

On Jan 3, 2016, at 9:09 PM, Adriano Ferreira <adriano.ferreira@me.com> wrote:

Hey there!

As a suggestion, check out this simple yet very interesting project called Then <https://github.com/devxoul/Then> by @devxoul.

Best,

— A

On Jan 3, 2016, at 1:37 AM, Weston Catron via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

Ability to write an initializer while initializing an object.

Example

let name = “John Apple”;
let person = Person {
    self.name = nameInput.first() + " " + nameInput.last()
    self.dob = dateInput.datetime()
    If (self.age() > 18) {
        self.taxableStatus = INDEPENDANT
    } else {
        self.taxableStatus = DEPENDANT
    }
};

Helpful examples: Objects with many required parameters that are defaulted in the initializers.

SKLabelNode

let label = SKLabelNode(text: "Example")
label.position = CGPoint(x: 0, y: 250);
label.fontSize = 34;
label.fontColor = UIColor.blackColor()
self.addChild(label);

Can become:

let label = SKLabelNode(text: self.package!.title) {
    self.position = CGPoint(x: 0, y: 250)
    self.fontSize = 34
    self.fontColor = UIColor.blackColor()
}
self.addChild(label)

Readability Instead of a large amount of code setting up temporary variables to pass into an initializer, all initializing code could be wrapped in a closure.

Flexibility Instead of exhaustive initializers covering many use cases. Simpler initializers can be extended as needed. This can also encourage required properties over optional ones that are immediately defined.

Compiler Warnings Closures react the same as initializers within classes, warning users of incomplete implementation of required properties.

Possible disadvantages:

Sloppy Coding Instead of writing complete initializers programmers can just rely on in-line initializers.

Tried Before I found this feature is also available in C# (https://msdn.microsoft.com/en-us/library/bb397680.aspx). Not sure if it was helpful then but many languages since don't appear to use it.

-Weston

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Jo Albright) #6

Here is another option. Attached is a playground I was messing around with. There are some weird bugs I was noticing, but don’t quite know if they are important enough to submit (comment out line 54 to see).

I actually like using the $0 so as to allow access to self if using within another type (ex: view controller code below).

Please respond with any potential issues with the code I have written.

- Jo

protocol ClosureInit { init() }

extension ClosureInit {

    init(@noescape b: inout Self -> Void) { self.init(); b(&self) }
    
}

struct Person: ClosureInit {
    
    enum GenderType: String { case Male, Female }
    
    var age: Int = 0
    var gender: GenderType?
    var name: String?
    
}

let me = Person {
    
    $0.name = "Jo"
    $0.age = 32
    $0.gender = .Male
    
}

me.age // 32

extension Array: ClosureInit { }

let randomIntArray = [Int] {
    
    for _ in 0...10 {
        
        $0.append(Int(arc4random_uniform(100)))
        
    }
    
}

randomIntArray

let personArray = [Person] {

    for _ in 0...8 {
        
        $0.append(Person {
            
            $0.age = Int(arc4random_uniform(100))
            $0.gender = Int(arc4random_uniform(100)) % 2 == 0 ? .Male : .Female // comment this line out to see error
            
        })
        
    }

}

personArray

extension UIView: ClosureInit { }

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        UILabel {
            
            $0.text = "This is Awesome!"
            $0.textColor = UIColor.cyanColor()
            $0.frame = CGRect(x: 20, y: 20, width: view.frame.width - 40, height: 40)
            view.addSubview($0)
            
        }
        
        view.addSubview(UIButton {
            
            $0.setTitle("Submit", forState: .Normal)
            $0.frame = CGRect(x: 20, y: 60, width: view.frame.width - 40, height: 40)
            
        })
        
    }
    
}

let vc = ViewController()

vc.loadViewIfNeeded()

vc.view.subviews

ClosureInit.playground.zip (11.5 KB)


(Thorsten Seitz) #7

Very nice!
I also think that using $0 is fine as it avoids all the problems with shadowing the outer self and thereby avoids erros and increases readability.

-Thorsten

···

Am 04.01.2016 um 18:51 schrieb Jo Albright via swift-evolution <swift-evolution@swift.org>:

Here is another option. Attached is a playground I was messing around with. There are some weird bugs I was noticing, but don’t quite know if they are important enough to submit (comment out line 54 to see).

I actually like using the $0 so as to allow access to self if using within another type (ex: view controller code below).

Please respond with any potential issues with the code I have written.

- Jo

protocol ClosureInit { init() }

extension ClosureInit {

    init(@noescape b: inout Self -> Void) { self.init(); b(&self) }
    
}

struct Person: ClosureInit {
    
    enum GenderType: String { case Male, Female }
    
    var age: Int = 0
    var gender: GenderType?
    var name: String?
    
}

let me = Person {
    
    $0.name = "Jo"
    $0.age = 32
    $0.gender = .Male
    
}

me.age // 32

extension Array: ClosureInit { }

let randomIntArray = [Int] {
    
    for _ in 0...10 {
        
        $0.append(Int(arc4random_uniform(100)))
        
    }
    
}

randomIntArray

let personArray = [Person] {

    for _ in 0...8 {
        
        $0.append(Person {
            
            $0.age = Int(arc4random_uniform(100))
            $0.gender = Int(arc4random_uniform(100)) % 2 == 0 ? .Male : .Female // comment this line out to see error
            
        })
        
    }

}

personArray

extension UIView: ClosureInit { }

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        UILabel {
            
            $0.text = "This is Awesome!"
            $0.textColor = UIColor.cyanColor()
            $0.frame = CGRect(x: 20, y: 20, width: view.frame.width - 40, height: 40)
            view.addSubview($0)
            
        }
        
        view.addSubview(UIButton {
            
            $0.setTitle("Submit", forState: .Normal)
            $0.frame = CGRect(x: 20, y: 60, width: view.frame.width - 40, height: 40)
            
        })
        
    }
    
}

let vc = ViewController()

vc.loadViewIfNeeded()

vc.view.subviews

<ClosureInit.playground.zip>

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Weston Catron) #8

Jo-

Sorry for such a delayed response. $0 is interesting, is this a common language concept elsewhere? I would still push for using the variable name or possibly a new keyword (this, self2, it), which is really just a more expressive $0. Or don’t keep self and just stick with the limitation of having to pass through any necessary information in the scope. In javascript it is common, albeit redundant/ugly, to do something like:

var that = this; 
$(…).each(function() { 
    this.foo(that) 
});

$0 is sort of an implied ` let $0 = self; ` before each closure.

Alternatively although this is sort of reverting back to a more verbose style we could specifically give our scope.

let me = person (name: “Weston”, dob: ..., that: self) {
    self.name = name;
    self.age = that.getAgeSinceDOB(dob);
    self.gender = that.calculateGender();
}

However I do not like this.

What do you think about a key word to replace $0 that is more expressive and easy to understand for new programmers? Or do you think recasting self only when necessary would be logical?

Weston

···

Here is another option. Attached is a playground I was messing around with. There are some weird bugs I was noticing, but don’t quite know if they are important enough to submit (comment out line 54 to see).

I actually like using the $0 so as to allow access to self if using within another type (ex: view controller code below).

Please respond with any potential issues with the code I have written.

- Jo

protocol ClosureInit { init() }

extension ClosureInit {

init(@noescape b: inout Self ->Void) { self.init(); b(&self) }

}

struct Person: ClosureInit {

enum GenderType: String { case Male, Female }

var age: Int = 0
var gender: GenderType?
var name: String?

}

let me = Person {

$0.name = "Jo"
$0.age = 32
$0.gender = .Male

}

me.age // 32

extension Array: ClosureInit { }

let randomIntArray = [Int] {

for _ in 0...10 {

$0.append(Int(arc4random_uniform(100)))

}

}

randomIntArray

let personArray = [Person] {

for _ in 0...8 {

$0.append(Person {

$0.age = Int(arc4random_uniform(100))
$0.gender = Int(arc4random_uniform(100)) % 2 == 0 ? .Male : .Female // comment this line out to see error

})

}

}

personArray

extension UIView: ClosureInit { }

class ViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()

UILabel {

$0.text = "This is Awesome!"
$0.textColor = UIColor.cyanColor()
$0.frame = CGRect(x: 20, y: 20, width: view.frame.width - 40, height: 40)
view.addSubview($0)

}

view.addSubview(UIButton {

$0.setTitle("Submit", forState: .Normal)
$0.frame = CGRect(x: 20, y: 60, width: view.frame.width - 40, height: 40)

})

}

}

let vc = ViewController()

vc.loadViewIfNeeded()

vc.view.subviews