[Proposal] Make `didSet` also available for `let` property, or something new such as `didInit`


(shengjia wang) #1

Since swift v1.2, we can initialize `let` property in `init()` instead of
being forced to give a value when declare it. This is great !

But every time I run into the case such as the example below:

let view: UIView {
  didSet {
    /**
     * This time, `view` did set ( a.k.a initialized in case of `let`
property), so I want to bind some other actions just after, such as
`setBackgroundColor`. But actually I can't, compiler will complain that
`let` declaration can not be observing properties. So I have to either move
all these "actions" to `init()` or change `view` to a `var` property which
is not necessary at all.
     */
  }
}

Actually in swift, I think it's quite commun issue that people run into a
large `init()` method. This approach could make it way better in most cases.

So, I'm wondering why not make `didSet` also available for `let` property,
or maybe even better to add new keyword such as "didInit" which only get
called for first set.

- Victor Wang


(Félix Cloutier) #2

willSet and didSet are currently not even called from the init method. This also goes counter to the current property behaviors proposal (didGet and didSet would become part of that) because behaviors aren't planned for let properties.

You're saying you want it to happen as soon as it was set, but do you really need it "as soon as that" or can you afford to wait a little bit? Because you can probably just put most of that code at the end of your initializer, where it's guaranteed that the property has been set.

Félix

···

Le 23 déc. 2015 à 10:22:46, shengjia wang via swift-evolution <swift-evolution@swift.org> a écrit :

Since swift v1.2, we can initialize `let` property in `init()` instead of being forced to give a value when declare it. This is great !

But every time I run into the case such as the example below:

let view: UIView {
  didSet {
    /**
     * This time, `view` did set ( a.k.a initialized in case of `let` property), so I want to bind some other actions just after, such as `setBackgroundColor`. But actually I can't, compiler will complain that `let` declaration can not be observing properties. So I have to either move all these "actions" to `init()` or change `view` to a `var` property which is not necessary at all.
     */
  }
}

Actually in swift, I think it's quite commun issue that people run into a large `init()` method. This approach could make it way better in most cases.

So, I'm wondering why not make `didSet` also available for `let` property, or maybe even better to add new keyword such as "didInit" which only get called for first set.

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


(Michel Fortin) #3

And they can't, because in those two blocks you have access to the old value as well as the new value. How would that work when you're setting the initial value?

  var value: Int {
    willSet { print("willSet \(value) -> \(newValue)") }
    didSet { print("didSet \(oldValue) -> \(value)") }
  }

···

Le 23 déc. 2015 à 11:07, Félix Cloutier via swift-evolution <swift-evolution@swift.org> a écrit :

willSet and didSet are currently not even called from the init method.

--
Michel Fortin
https://michelf.ca


(shengjia wang) #4

Hi,

You are saying: *"Because you can probably just put most of that code at
the end of your initialiser"*. I see.. and this proposal is exactly about
how to avoid this situation.

I'm saying it would be neat if we can band some side effects once we set a
`let` property. Example:

    let view: UIView {

        didSet {

            view.background = UIColor.blackColor()

            view.translatesAutoresizingMaskIntoConstraints

        }

    }

    let scrollView: UIScrollView {

        willSet {

            scrollView.removeObserver(self, forKeyPath: "contentOffset")

        }

        didSet {

            scrollView.addObserver(self, forKeyPath: "contentOffset",
options: .New, context: nil)

        }

    }

    init(targetScrollView: UIScrollView) {

        view = UIView()

        scrollView = targetScrollView

        super.init()

        // if we could put them into property observing ...

        // view.background = UIColor.blackColor()

        // view.translatesAutoresizingMaskIntoConstraints

        // scrollView.addObserver(self, forKeyPath: "contentOffset",
options:.New, context: nil)

        // ...

    }

We can extract the "setup property" step from init method and separate them
for all different properties just after the property be initialised.
Otherwise, we have to either mix them into the init method or change `let`
to `var`. Both are doable but not ideal in my opinion.

By the way, the principal of this idea is similar to another approach for
IBOutlet property
<https://twitter.com/jesse_squires/status/626264940450480128> :

Use *didSet* on your IBOutlets to configure views instead of cramming code

into viewDidLoad. Much cleaner. Still called only once.

- Victor Wang

···

On Wed, Dec 23, 2015 at 5:07 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

willSet and didSet are currently not even called from the init method.
This also goes counter to the current property behaviors proposal (didGet
and didSet would become part of that) because behaviors aren't planned for
let properties.

You're saying you want it to happen as soon as it was set, but do you
really need it "as soon as that" or can you afford to wait a little bit?
Because you can probably just put most of that code at the end of your
initializer, where it's guaranteed that the property has been set.

Félix

Le 23 déc. 2015 à 10:22:46, shengjia wang via swift-evolution < > swift-evolution@swift.org> a écrit :

Since swift v1.2, we can initialize `let` property in `init()` instead of
being forced to give a value when declare it. This is great !

But every time I run into the case such as the example below:

let view: UIView {
  didSet {
    /**
     * This time, `view` did set ( a.k.a initialized in case of `let`
property), so I want to bind some other actions just after, such as
`setBackgroundColor`. But actually I can't, compiler will complain that
`let` declaration can not be observing properties. So I have to either move
all these "actions" to `init()` or change `view` to a `var` property which
is not necessary at all.
     */
  }
}

Actually in swift, I think it's quite commun issue that people run into a
large `init()` method. This approach could make it way better in most cases.

So, I'm wondering why not make `didSet` also available for `let` property,
or maybe even better to add new keyword such as "didInit" which only get
called for first set.

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


(shengjia wang) #5

@Félix,

For the first point, the issue you talked about isn't new. We can enter
into that mess easily right now just by changing `let` to `var`.

For the second point, well.. I agree with you and @Michel, thank you guys.
It do make sense, I should've noticed it earlier. This is not an
appropriate solution to make the current "didSet" keyword available for
`let` property.

But I'm still thinking it should not be a problem at all if we add
something new, such as "didInit", which only get called for the very first
time when property is assigned a value.

What do you think?

···

On Wed, Dec 23, 2015 at 7:39 PM, Michel Fortin <michel.fortin@michelf.ca> wrote:

Le 23 déc. 2015 à 11:07, Félix Cloutier via swift-evolution < > swift-evolution@swift.org> a écrit :

> willSet and didSet are currently not even called from the init method.

And they can't, because in those two blocks you have access to the old
value as well as the new value. How would that work when you're setting the
initial value?

        var value: Int {
                willSet { print("willSet \(value) -> \(newValue)") }
                didSet { print("didSet \(oldValue) -> \(value)") }
        }

--
Michel Fortin
https://michelf.ca


(Félix Cloutier) #6

I'm just one person out of many on this list that you can convince. However, it's gonna be an uphill battle to get my vote. I see the small clarity benefit that this brings but I also see big problems that you didn't mention.

First, property observers are currently not fired from initializers. This is because it is unreasonably constraining that observers should be able to run even though your object has not been fully initialized. What if they need another variable that has not been initialized? It is not possible to create a migration tool that would automatically fix these issues, as you can have multiple initializers that initialize members in a different order. Is it worth it to potentially break every existing Swift code base to be able to move initialization code a few lines up?

This also does not address the case where let properties are initialized in a different order in different initializers. You could easily end up with observers that simply cannot run at the time that they should.

Second, let properties cannot have observers attached to it for reasons that are obvious in the current model (observers don't fire in init methods and let properties cannot be modified outside the init method). Your proposal would add them for the sole purpose of using them in the init method, since they cannot fire from anywhere else. There is also a very interesting proposal to generalize property behaviors <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20151214/003148.html>, which may structurally transform how willSet and didSet are implemented, and this proposal currently excludes behaviors on let properties.

It's not necessarily a bad idea to move the code elsewhere, but I don't think that this is an appropriate solution.

Félix

···

Le 23 déc. 2015 à 12:39:10, shengjia wang <wangshengjia01@gmail.com> a écrit :

Hi,

You are saying: "Because you can probably just put most of that code at the end of your initialiser". I see.. and this proposal is exactly about how to avoid this situation.

I'm saying it would be neat if we can band some side effects once we set a `let` property. Example:

    let view: UIView {
        didSet {
            view.background = UIColor.blackColor()
            view.translatesAutoresizingMaskIntoConstraints
        }
    }
    let scrollView: UIScrollView {
        willSet {
            scrollView.removeObserver(self, forKeyPath: "contentOffset")
        }
        didSet {
            scrollView.addObserver(self, forKeyPath: "contentOffset", options: .New, context: nil)
        }
    }
    init(targetScrollView: UIScrollView) {
        view = UIView()
        scrollView = targetScrollView
        super.init()
        
        // if we could put them into property observing ...
        // view.background = UIColor.blackColor()
        // view.translatesAutoresizingMaskIntoConstraints
        // scrollView.addObserver(self, forKeyPath: "contentOffset", options:.New, context: nil)
        // ...
    }

We can extract the "setup property" step from init method and separate them for all different properties just after the property be initialised. Otherwise, we have to either mix them into the init method or change `let` to `var`. Both are doable but not ideal in my opinion.

By the way, the principal of this idea is similar to another approach for IBOutlet property <https://twitter.com/jesse_squires/status/626264940450480128> :

Use didSet on your IBOutlets to configure views instead of cramming code into viewDidLoad. Much cleaner. Still called only once.

- Victor Wang

On Wed, Dec 23, 2015 at 5:07 PM, Félix Cloutier <felixcca@yahoo.ca <mailto:felixcca@yahoo.ca>> wrote:
willSet and didSet are currently not even called from the init method. This also goes counter to the current property behaviors proposal (didGet and didSet would become part of that) because behaviors aren't planned for let properties.

You're saying you want it to happen as soon as it was set, but do you really need it "as soon as that" or can you afford to wait a little bit? Because you can probably just put most of that code at the end of your initializer, where it's guaranteed that the property has been set.

Félix

Le 23 déc. 2015 à 10:22:46, shengjia wang via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

Since swift v1.2, we can initialize `let` property in `init()` instead of being forced to give a value when declare it. This is great !

But every time I run into the case such as the example below:

let view: UIView {
  didSet {
    /**
     * This time, `view` did set ( a.k.a initialized in case of `let` property), so I want to bind some other actions just after, such as `setBackgroundColor`. But actually I can't, compiler will complain that `let` declaration can not be observing properties. So I have to either move all these "actions" to `init()` or change `view` to a `var` property which is not necessary at all.
     */
  }
}

Actually in swift, I think it's quite commun issue that people run into a large `init()` method. This approach could make it way better in most cases.

So, I'm wondering why not make `didSet` also available for `let` property, or maybe even better to add new keyword such as "didInit" which only get called for first set.

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


(Michal Pearse) #7

I feel like this would be an unexpected way of doing things.

Why would I want to separate my set-up code from my init() without any
immediately obvious link between the two?

If your init() is getting unwieldy, perhaps a cleaner solution is to simply
decompose some of the setting up into smaller (private) methods that are
then called by the init()? At least when it is done this way, you can
immediately see that other stuff happens from within the init() method
without having to think about whether a will/didSet might have been called
on a property.

···

On Thu, Dec 24, 2015 at 6:39 AM, shengjia wang via swift-evolution < swift-evolution@swift.org> wrote:

Hi,

You are saying: *"Because you can probably just put most of that code at
the end of your initialiser"*. I see.. and this proposal is exactly about
how to avoid this situation.

I'm saying it would be neat if we can band some side effects once we set a
`let` property. Example:

    let view: UIView {

        didSet {

            view.background = UIColor.blackColor()

            view.translatesAutoresizingMaskIntoConstraints

        }

    }

    let scrollView: UIScrollView {

        willSet {

            scrollView.removeObserver(self, forKeyPath: "contentOffset")

        }

        didSet {

            scrollView.addObserver(self, forKeyPath: "contentOffset",
options: .New, context: nil)

        }

    }

    init(targetScrollView: UIScrollView) {

        view = UIView()

        scrollView = targetScrollView

        super.init()

        // if we could put them into property observing ...

        // view.background = UIColor.blackColor()

        // view.translatesAutoresizingMaskIntoConstraints

        // scrollView.addObserver(self, forKeyPath: "contentOffset",
options:.New, context: nil)

        // ...

    }

We can extract the "setup property" step from init method and separate
them for all different properties just after the property be initialised.
Otherwise, we have to either mix them into the init method or change `let`
to `var`. Both are doable but not ideal in my opinion.

By the way, the principal of this idea is similar to another approach for
IBOutlet property
<https://twitter.com/jesse_squires/status/626264940450480128> :

Use *didSet* on your IBOutlets to configure views instead of cramming

code into viewDidLoad. Much cleaner. Still called only once.

- Victor Wang

On Wed, Dec 23, 2015 at 5:07 PM, Félix Cloutier <felixcca@yahoo.ca> wrote:

willSet and didSet are currently not even called from the init method.
This also goes counter to the current property behaviors proposal (didGet
and didSet would become part of that) because behaviors aren't planned for
let properties.

You're saying you want it to happen as soon as it was set, but do you
really need it "as soon as that" or can you afford to wait a little bit?
Because you can probably just put most of that code at the end of your
initializer, where it's guaranteed that the property has been set.

Félix

Le 23 déc. 2015 à 10:22:46, shengjia wang via swift-evolution < >> swift-evolution@swift.org> a écrit :

Since swift v1.2, we can initialize `let` property in `init()` instead of
being forced to give a value when declare it. This is great !

But every time I run into the case such as the example below:

let view: UIView {
  didSet {
    /**
     * This time, `view` did set ( a.k.a initialized in case of `let`
property), so I want to bind some other actions just after, such as
`setBackgroundColor`. But actually I can't, compiler will complain that
`let` declaration can not be observing properties. So I have to either move
all these "actions" to `init()` or change `view` to a `var` property which
is not necessary at all.
     */
  }
}

Actually in swift, I think it's quite commun issue that people run into a
large `init()` method. This approach could make it way better in most cases.

So, I'm wondering why not make `didSet` also available for `let`
property, or maybe even better to add new keyword such as "didInit" which
only get called for first set.

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

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