[Discussion] Difference between static and lazy variables regarding evaluation of closure


(David Rönnqvist) #1

I noticed a difference between how static and lazy variables evaluate closures and thought that it was a bug:
https://bugs.swift.org/browse/SR-1178
but apparently it’s not.

The difference can be illustrated in a small example. The following code will evaluate the closure for the static variable, even when *setting* the variable, but won’t evaluate the closure for the lazy variable. Thus, it prints “static”, but not “lazy”.

    class Foo {
        static var bar: String = {
            print("static")
            return "Default"
        }()
        
        lazy var baz: String = {
            print("lazy")
            return "Lazy"
        }()
    }

    Foo.bar = "Set"

    let foo = Foo()
    foo.baz = “Set"

I would have thought that neither case should evaluate the closure when setting the variable, since the result from the closure is never used in that case. I don’t feel that strongly about if the closure is evaluated or not. But I would like both types (static and lazy) to behave the same.

- David


(Vladimir) #2

Yes, IMO it really looks strange.
Just checked:

     class Foo {
         static var test = "test"

         static var bar: String = {
             print("static")
             return "Default"
         }()

         lazy var baz: String = {
             print("lazy")
             return "Lazy"
         }()
     }

     print("1")
     print(Foo.test)
     print("2")
     Foo.bar = "Set"
     print("3")
     let foo = Foo()
     foo.baz = "Set"
     print("4")

we have :
1
test
2
static
3
4

I strongly believe as static property is lazy by definition, it must not be evaluated at all when we set it. This is something that "lazyness" promises to us - that it will be called/calculated ONLY when we ask for this. So in my opinion this is bug/issue and should be fixed/changed in Swift 3.0.

···

On 08.04.2016 10:36, David Rönnqvist via swift-evolution wrote:

I noticed a difference between how static and lazy variables evaluate closures and thought that it was a bug:
https://bugs.swift.org/browse/SR-1178
but apparently it’s not.

> ...


(David Rönnqvist) #3

With the risk of this showing up 3 times in the mailing list (I posted it as a new topic, but it didn't seem to show up).

Several weeks ago I posted that I was confused by the differences between how static variables and lazy variables evaluated (or - as I would expect - didn’t evaluate) when initially assigned with a different value. It didn’t result in any discussion, but I encountered it again and decided to draft a proposal hoping that there will be some discussion around it. The draft is available here: https://github.com/d-ronnqvist/swift-evolution/blob/master/proposals/0000-static-var-lazy-behavior.md

Please let me know what you think, and if you believe this is something that’s worth pursuing.

- David

Lazy evaluation when assigning static variables

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-name.md>
Author(s): David Rönnqvist <https://github.com/d-ronnqvist>
Status: Awaiting review <https://github.com/d-ronnqvist/swift-evolution#rationale>
Review manager: TBD
<https://github.com/d-ronnqvist/swift-evolution#introduction>Introduction

Both stored type properties (static) and lazy stored properties (lazy var) are lazily initialized. However, they have different initialization behavior in that stored type properties evaluate even when assigning them a new value.

The following code will print "static", but not "lazy":

class Foo {
    static var bar: String = {
        print("static")
        return "Default"
    }()

    lazy var baz: String = {
        print("lazy")
        return "Lazy"
    }()
}

Foo.bar = "Set" // this evaluates the initial value of `bar` before setting a new value

let foo = Foo()
foo.baz = "Set" // this doesn't evaluate `baz` before setting a new value
Swift-evolution thread: [Discussion] Difference between static and lazy variables regarding evaluation of closure <http://thread.gmane.org/gmane.comp.lang.swift.evolution/14086>
<https://github.com/d-ronnqvist/swift-evolution#motivation>Motivation

Swift currently evaluates stored type properties even when assigning a new value. This behavior is very subtle and can lead to objects being needlessly initialized and "immediately" de-initialized, as well as unwanted side effects (caused by the initialized objects).

For example, a shared re-assignable instance that is replaced during unit test set up will initialize the "real" object before assigning the test replacement.

<https://github.com/d-ronnqvist/swift-evolution#detailed-design>Detailed design

This proposal seeks to unify the lazy evaluation on assignment of stored type properties (static) and lazy stored properties (lazy var) so that the value being replaced isn't evaluated (the current behavior of lazy stored properties).

However, it seeks to keep their respective behaviors and guarantees regarding multithreaded simultaneous access:

From the The Swift Programming Language (Swift 2.2) <https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID254> regarding lazy stored properties:

If a property marked with the lazy modifier is accessed by multiple threads simultaneously and the property has not yet been initialized, there is no guarantee that the property will be initialized only once.
and regarding stored type properties:

Stored type properties are lazily initialized on their first access. They are guaranteed to be initialized only once, even when accessed by multiple threads simultaneously, and they do not need to be marked with the lazy modifier.
No changes to the syntax is proposed.

This provides a more consistent lazy evaluation behavior, and fixes a (small) source of potential, subtle bugs.

<https://github.com/d-ronnqvist/swift-evolution#impact-on-existing-code>Impact on existing code

This proposal changes the lazy evaluation of stored type properties when assigning a new value.

Any code that is relying on this effect would break in subtle ways. This is hard to detect and migrate, but hopefully very rare (and to the best of my knowledge the behavior that code would be relying upon is undocumented).

<https://github.com/d-ronnqvist/swift-evolution#alternatives-considered>Alternatives considered

One alternative is to be consistent with the stored type properties and always evaluate the initial value, even when re-assigning it. However, this version doesn't address the subtle bugs that can arise from this behavior.

Another alternative is to leave the the respective behaviors as is and mention their differences in The Swift Programming Language guide. This might still be the most viable alternative in case the current behavior is a consequence of their respective implementations with regards to multithreaded access.

<https://github.com/d-ronnqvist/swift-evolution#rationale>Rationale

On [Date], the core team decided to (TBD) this proposal. When the core team makes a decision regarding this proposal, their rationale for the decision will be written here.

8 apr. 2016 kl. 18:50 skrev Vladimir.S via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

···

Yes, IMO it really looks strange.
Just checked:

   class Foo {
       static var test = "test"

       static var bar: String = {
           print("static")
           return "Default"
       }()

       lazy var baz: String = {
           print("lazy")
           return "Lazy"
       }()
   }

   print("1")
   print(Foo.test)
   print("2")
   Foo.bar = "Set"
   print("3")
   let foo = Foo()
   foo.baz = "Set"
   print("4")

we have :
1
test
2
static
3
4

I strongly believe as static property is lazy by definition, it must not be evaluated at all when we set it. This is something that "lazyness" promises to us - that it will be called/calculated ONLY when we ask for this. So in my opinion this is bug/issue and should be fixed/changed in Swift 3.0.

On 08.04.2016 10:36, David Rönnqvist via swift-evolution wrote:

I noticed a difference between how static and lazy variables evaluate closures and thought that it was a bug:
https://bugs.swift.org/browse/SR-1178
but apparently it’s not.

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


(Chris Lattner) #4

This seems like a step in the right direction, but may not big a step far enough. Keep in mind that “static” and global variables are quite different than lazy variables, both in terms of implementation and behavior but also in terms of planned future direction.

“lazy” is almost certainly to be replaced by property behaviors in the future, making them a library defined feature whose behavior can be changed arbitrarily, and can hopefully have useful additional functionality like a “reset()” method added to them. Global variables are currently not like that: their behavior is defined by compiler magic instead of by property semantics.

If the goal was to remove magic from the compiler, then a possible direction would be to do something like:

1) Introduce a new declmodifier named something like “atomiclazy”.
2) Disallow global and static variables, unless they are explicitly marked atomiclazy (compiler would provide a fixit hint to suggest this).
3) Replace the atomiclazy magic with a property behavior when they exist.

In this model, you’d presumably have the choice about atomiclazy or lazy when setting up either a static or a local property.

-Chris

···

On May 31, 2016, at 6:32 AM, David Rönnqvist via swift-evolution <swift-evolution@swift.org> wrote:
Lazy evaluation when assigning static variables

<https://github.com/d-ronnqvist/swift-evolution#introduction>Introduction

Both stored type properties (static) and lazy stored properties (lazy var) are lazily initialized. However, they have different initialization behavior in that stored type properties evaluate even when assigning them a new value.

The following code will print "static", but not "lazy":

class Foo {
    static var bar: String = {
        print("static")
        return "Default"
    }()

    lazy var baz: String = {
        print("lazy")
        return "Lazy"
    }()
}

Foo.bar = "Set" // this evaluates the initial value of `bar` before setting a new value

let foo = Foo()
foo.baz = "Set" // this doesn't evaluate `baz` before setting a new value


(Haravikk) #5

I’m a +1 for making the behaviour more consistent, I can’t imagine many people rely on the guaranteed execution for static properties (personally I wasn’t even aware there was a difference), and anyone that needs guaranteed execution should be implementing lazy properties manually (i.e- with computed properties) as it’s possible to trigger required code more efficiently that way.

So yeah, I think that execution only as required is the right choice and that these should definitely be made consistent.

···

On 31 May 2016, at 14:32, David Rönnqvist via swift-evolution <swift-evolution@swift.org> wrote:

With the risk of this showing up 3 times in the mailing list (I posted it as a new topic, but it didn't seem to show up).

Several weeks ago I posted that I was confused by the differences between how static variables and lazy variables evaluated (or - as I would expect - didn’t evaluate) when initially assigned with a different value. It didn’t result in any discussion, but I encountered it again and decided to draft a proposal hoping that there will be some discussion around it. The draft is available here: https://github.com/d-ronnqvist/swift-evolution/blob/master/proposals/0000-static-var-lazy-behavior.md

Please let me know what you think, and if you believe this is something that’s worth pursuing.

- David

Lazy evaluation when assigning static variables

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-name.md>
Author(s): David Rönnqvist <https://github.com/d-ronnqvist>
Status: Awaiting review <https://github.com/d-ronnqvist/swift-evolution#rationale>
Review manager: TBD
<https://github.com/d-ronnqvist/swift-evolution#introduction>Introduction

Both stored type properties (static) and lazy stored properties (lazy var) are lazily initialized. However, they have different initialization behavior in that stored type properties evaluate even when assigning them a new value.

The following code will print "static", but not "lazy":

class Foo {
    static var bar: String = {
        print("static")
        return "Default"
    }()

    lazy var baz: String = {
        print("lazy")
        return "Lazy"
    }()
}

Foo.bar = "Set" // this evaluates the initial value of `bar` before setting a new value

let foo = Foo()
foo.baz = "Set" // this doesn't evaluate `baz` before setting a new value
Swift-evolution thread: [Discussion] Difference between static and lazy variables regarding evaluation of closure <http://thread.gmane.org/gmane.comp.lang.swift.evolution/14086>
<https://github.com/d-ronnqvist/swift-evolution#motivation>Motivation

Swift currently evaluates stored type properties even when assigning a new value. This behavior is very subtle and can lead to objects being needlessly initialized and "immediately" de-initialized, as well as unwanted side effects (caused by the initialized objects).

For example, a shared re-assignable instance that is replaced during unit test set up will initialize the "real" object before assigning the test replacement.

<https://github.com/d-ronnqvist/swift-evolution#detailed-design>Detailed design

This proposal seeks to unify the lazy evaluation on assignment of stored type properties (static) and lazy stored properties (lazy var) so that the value being replaced isn't evaluated (the current behavior of lazy stored properties).

However, it seeks to keep their respective behaviors and guarantees regarding multithreaded simultaneous access:

From the The Swift Programming Language (Swift 2.2) <https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID254> regarding lazy stored properties:

If a property marked with the lazy modifier is accessed by multiple threads simultaneously and the property has not yet been initialized, there is no guarantee that the property will be initialized only once.
and regarding stored type properties:

Stored type properties are lazily initialized on their first access. They are guaranteed to be initialized only once, even when accessed by multiple threads simultaneously, and they do not need to be marked with the lazy modifier.
No changes to the syntax is proposed.

This provides a more consistent lazy evaluation behavior, and fixes a (small) source of potential, subtle bugs.

<https://github.com/d-ronnqvist/swift-evolution#impact-on-existing-code>Impact on existing code

This proposal changes the lazy evaluation of stored type properties when assigning a new value.

Any code that is relying on this effect would break in subtle ways. This is hard to detect and migrate, but hopefully very rare (and to the best of my knowledge the behavior that code would be relying upon is undocumented).

<https://github.com/d-ronnqvist/swift-evolution#alternatives-considered>Alternatives considered

One alternative is to be consistent with the stored type properties and always evaluate the initial value, even when re-assigning it. However, this version doesn't address the subtle bugs that can arise from this behavior.

Another alternative is to leave the the respective behaviors as is and mention their differences in The Swift Programming Language guide. This might still be the most viable alternative in case the current behavior is a consequence of their respective implementations with regards to multithreaded access.

<https://github.com/d-ronnqvist/swift-evolution#rationale>Rationale

On [Date], the core team decided to (TBD) this proposal. When the core team makes a decision regarding this proposal, their rationale for the decision will be written here.

8 apr. 2016 kl. 18:50 skrev Vladimir.S via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

Yes, IMO it really looks strange.
Just checked:

   class Foo {
       static var test = "test"

       static var bar: String = {
           print("static")
           return "Default"
       }()

       lazy var baz: String = {
           print("lazy")
           return "Lazy"
       }()
   }

   print("1")
   print(Foo.test)
   print("2")
   Foo.bar = "Set"
   print("3")
   let foo = Foo()
   foo.baz = "Set"
   print("4")

we have :
1
test
2
static
3
4

I strongly believe as static property is lazy by definition, it must not be evaluated at all when we set it. This is something that "lazyness" promises to us - that it will be called/calculated ONLY when we ask for this. So in my opinion this is bug/issue and should be fixed/changed in Swift 3.0.

On 08.04.2016 10:36, David Rönnqvist via swift-evolution wrote:

I noticed a difference between how static and lazy variables evaluate closures and thought that it was a bug:
https://bugs.swift.org/browse/SR-1178
but apparently it’s not.

> ...
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto: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


(Charlie Monroe) #6

Hi David,

I personally think that the behavior of static variables is correct. It may look like an abuse, but until we get a better support for singletons, something like this is quite a common pattern IMHO:

public private(set) static var currentSession: Session? = {
  let session = ... // Read stored session.
  if session.isExpired {
    session.clearCache()
    session.invalidateToken()
    return nil
  }
  
  return session
}()

- it is convenient to do some global initialization as well - it is guaranteed to be run only once and you may need to e.g. invalidate the session when reading it (expired, invalid, ...).

On the other hand, when it's an instance member (lazy var), this kind of initialization should be done in the initializer - there are currently no initializers for the metatype (unless you inherit from NSObject and override +initialize).

That said, I agree that the behavior is inconsistent, but I'd approach it from the other side - I'd vote for always executing the closure for lazy evaluation.

Your proposal would break a lot of existing code without any warning and would introduce very hard to catch bugs.

Charlie

···

On May 31, 2016, at 3:32 PM, David Rönnqvist via swift-evolution <swift-evolution@swift.org> wrote:

With the risk of this showing up 3 times in the mailing list (I posted it as a new topic, but it didn't seem to show up).

Several weeks ago I posted that I was confused by the differences between how static variables and lazy variables evaluated (or - as I would expect - didn’t evaluate) when initially assigned with a different value. It didn’t result in any discussion, but I encountered it again and decided to draft a proposal hoping that there will be some discussion around it. The draft is available here: https://github.com/d-ronnqvist/swift-evolution/blob/master/proposals/0000-static-var-lazy-behavior.md

Please let me know what you think, and if you believe this is something that’s worth pursuing.

- David

Lazy evaluation when assigning static variables

Proposal: SE-NNNN <https://github.com/apple/swift-evolution/blob/master/proposals/NNNN-name.md>
Author(s): David Rönnqvist <https://github.com/d-ronnqvist>
Status: Awaiting review <https://github.com/d-ronnqvist/swift-evolution#rationale>
Review manager: TBD
<https://github.com/d-ronnqvist/swift-evolution#introduction>Introduction

Both stored type properties (static) and lazy stored properties (lazy var) are lazily initialized. However, they have different initialization behavior in that stored type properties evaluate even when assigning them a new value.

The following code will print "static", but not "lazy":

class Foo {
    static var bar: String = {
        print("static")
        return "Default"
    }()

    lazy var baz: String = {
        print("lazy")
        return "Lazy"
    }()
}

Foo.bar = "Set" // this evaluates the initial value of `bar` before setting a new value

let foo = Foo()
foo.baz = "Set" // this doesn't evaluate `baz` before setting a new value
Swift-evolution thread: [Discussion] Difference between static and lazy variables regarding evaluation of closure <http://thread.gmane.org/gmane.comp.lang.swift.evolution/14086>
<https://github.com/d-ronnqvist/swift-evolution#motivation>Motivation

Swift currently evaluates stored type properties even when assigning a new value. This behavior is very subtle and can lead to objects being needlessly initialized and "immediately" de-initialized, as well as unwanted side effects (caused by the initialized objects).

For example, a shared re-assignable instance that is replaced during unit test set up will initialize the "real" object before assigning the test replacement.

<https://github.com/d-ronnqvist/swift-evolution#detailed-design>Detailed design

This proposal seeks to unify the lazy evaluation on assignment of stored type properties (static) and lazy stored properties (lazy var) so that the value being replaced isn't evaluated (the current behavior of lazy stored properties).

However, it seeks to keep their respective behaviors and guarantees regarding multithreaded simultaneous access:

From the The Swift Programming Language (Swift 2.2) <https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID254> regarding lazy stored properties:

If a property marked with the lazy modifier is accessed by multiple threads simultaneously and the property has not yet been initialized, there is no guarantee that the property will be initialized only once.
and regarding stored type properties:

Stored type properties are lazily initialized on their first access. They are guaranteed to be initialized only once, even when accessed by multiple threads simultaneously, and they do not need to be marked with the lazy modifier.
No changes to the syntax is proposed.

This provides a more consistent lazy evaluation behavior, and fixes a (small) source of potential, subtle bugs.

<https://github.com/d-ronnqvist/swift-evolution#impact-on-existing-code>Impact on existing code

This proposal changes the lazy evaluation of stored type properties when assigning a new value.

Any code that is relying on this effect would break in subtle ways. This is hard to detect and migrate, but hopefully very rare (and to the best of my knowledge the behavior that code would be relying upon is undocumented).

<https://github.com/d-ronnqvist/swift-evolution#alternatives-considered>Alternatives considered

One alternative is to be consistent with the stored type properties and always evaluate the initial value, even when re-assigning it. However, this version doesn't address the subtle bugs that can arise from this behavior.

Another alternative is to leave the the respective behaviors as is and mention their differences in The Swift Programming Language guide. This might still be the most viable alternative in case the current behavior is a consequence of their respective implementations with regards to multithreaded access.

<https://github.com/d-ronnqvist/swift-evolution#rationale>Rationale

On [Date], the core team decided to (TBD) this proposal. When the core team makes a decision regarding this proposal, their rationale for the decision will be written here.

8 apr. 2016 kl. 18:50 skrev Vladimir.S via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>>:

Yes, IMO it really looks strange.
Just checked:

   class Foo {
       static var test = "test"

       static var bar: String = {
           print("static")
           return "Default"
       }()

       lazy var baz: String = {
           print("lazy")
           return "Lazy"
       }()
   }

   print("1")
   print(Foo.test)
   print("2")
   Foo.bar = "Set"
   print("3")
   let foo = Foo()
   foo.baz = "Set"
   print("4")

we have :
1
test
2
static
3
4

I strongly believe as static property is lazy by definition, it must not be evaluated at all when we set it. This is something that "lazyness" promises to us - that it will be called/calculated ONLY when we ask for this. So in my opinion this is bug/issue and should be fixed/changed in Swift 3.0.

On 08.04.2016 10:36, David Rönnqvist via swift-evolution wrote:

I noticed a difference between how static and lazy variables evaluate closures and thought that it was a bug:
https://bugs.swift.org/browse/SR-1178
but apparently it’s not.

> ...
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto: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


(Vladimir) #7

David, I personally see all 3 of your copies of proposal in list :wink:
+1 from me for this proposal. It removes inconsistency and removes unexpected behavior. Known that static stored property is lazy(I'd like to require `lazy` keyword for static stored properties to highlight that fact), I believe no one really expects that it will be evaluated on assignment. Current behavior looks like a bug for me.

···

On 31.05.2016 16:32, David Rönnqvist wrote:

With the risk of this showing up 3 times in the mailing list (I posted it
as a new topic, but it didn't seem to show up).


(Joe Groff) #8

Lazy evaluation when assigning static variables

Introduction

Both stored type properties (static) and lazy stored properties (lazy var) are lazily initialized. However, they have different initialization behavior in that stored type properties evaluate even when assigning them a new value.

The following code will print "static", but not "lazy":

class
Foo {
    
static var bar: String =
{
        
print("static"
)
        
return "Default"

    }()

lazy var baz: String =
{
        
print("lazy"
)
        
return "Lazy"

    }()
}

Foo
.bar = "Set" // this evaluates the initial value of `bar` before setting a new value

let foo =
Foo()
foo
.baz = "Set" // this doesn't evaluate `baz` before setting a new value

This seems like a step in the right direction, but may not big a step far enough. Keep in mind that “static” and global variables are quite different than lazy variables, both in terms of implementation and behavior but also in terms of planned future direction.

“lazy” is almost certainly to be replaced by property behaviors in the future, making them a library defined feature whose behavior can be changed arbitrarily, and can hopefully have useful additional functionality like a “reset()” method added to them. Global variables are currently not like that: their behavior is defined by compiler magic instead of by property semantics.

If the goal was to remove magic from the compiler, then a possible direction would be to do something like:

1) Introduce a new declmodifier named something like “atomiclazy”.
2) Disallow global and static variables, unless they are explicitly marked atomiclazy (compiler would provide a fixit hint to suggest this).
3) Replace the atomiclazy magic with a property behavior when they exist.

This doesn't make sense. This "magic" is the only sensible way to initialize a global that requires dynamic initialization. Even with property behaviors, we would need to lazily apply the behavior's initialization logic to get the property into its initial state. Nonatomic lazy would be a misoptimization—it's hard to beat dispatch_once at its own game.

-Joe

···

On May 31, 2016, at 12:37 PM, Chris Lattner via swift-evolution <swift-evolution@swift.org> wrote:

On May 31, 2016, at 6:32 AM, David Rönnqvist via swift-evolution <swift-evolution@swift.org> wrote:

In this model, you’d presumably have the choice about atomiclazy or lazy when setting up either a static or a local property.

-Chris

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


(David Rönnqvist) #9

Did I understand it correctly that, under such a proposal, there would be no compiler magic for global properties and instead both global, type, and instance properties would be controlled by property behaviors (or lazy/atomiclazy until they are replaced by property behaviors)?

`lazy` having he current a behavior of lazy properties (no threading guarantees, not evaluating on assignment),
`atomiclazy` having the current behavior of static properties (guaranteed to only evaluate once when accessed from multiple threads, evaluating even on assignment)

- David

···

31 maj 2016 kl. 21:37 skrev Chris Lattner <clattner@apple.com>:

On May 31, 2016, at 6:32 AM, David Rönnqvist via swift-evolution <swift-evolution@swift.org> wrote:
Lazy evaluation when assigning static variables
Introduction
Both stored type properties (static) and lazy stored properties (lazy var) are lazily initialized. However, they have different initialization behavior in that stored type properties evaluate even when assigning them a new value.

The following code will print "static", but not "lazy":

class Foo {
    static var bar: String = {
        print("static")
        return "Default"
    }()

    lazy var baz: String = {
        print("lazy")
        return "Lazy"
    }()
}

Foo.bar = "Set" // this evaluates the initial value of `bar` before setting a new value

let foo = Foo()
foo.baz = "Set" // this doesn't evaluate `baz` before setting a new value

This seems like a step in the right direction, but may not big a step far enough. Keep in mind that “static” and global variables are quite different than lazy variables, both in terms of implementation and behavior but also in terms of planned future direction.

“lazy” is almost certainly to be replaced by property behaviors in the future, making them a library defined feature whose behavior can be changed arbitrarily, and can hopefully have useful additional functionality like a “reset()” method added to them. Global variables are currently not like that: their behavior is defined by compiler magic instead of by property semantics.

If the goal was to remove magic from the compiler, then a possible direction would be to do something like:

1) Introduce a new declmodifier named something like “atomiclazy”.
2) Disallow global and static variables, unless they are explicitly marked atomiclazy (compiler would provide a fixit hint to suggest this).
3) Replace the atomiclazy magic with a property behavior when they exist.

In this model, you’d presumably have the choice about atomiclazy or lazy when setting up either a static or a local property.

-Chris


(Vladimir) #10

OK, I like this direction. I.e. instead of changing current behavior of static/global variables, we require explicit decoration for them with `atomiclazy` and so their behavior becomes self-documented and it is clear from definition that this behavior is different than just `lazy`. One can't expect that `atomiclazy` is the same as `lazy`.

This appends some complexity to the language to syntax but also add clarity and self-documentation to behavior of defined static/global variables.

As I understand, we need this `atomiclazy` only if static variable has initialization block like here:

class A {
     static atomiclazy var p : Int = {10}()
}

but this will not require atomiclazy :

class A {
     static var p = 10
}

Opinions on this?

···

On 31.05.2016 22:37, Chris Lattner wrote:

1) Introduce a new declmodifier named something like “atomiclazy”.
2) Disallow global and static variables, unless they are explicitly marked
atomiclazy (compiler would provide a fixit hint to suggest this).
3) Replace the atomiclazy magic with a property behavior when they exist.

In this model, you’d presumably have the choice about atomiclazy or lazy
when setting up either a static or a local property.


(Chris Lattner) #11

Why couldn’t a "sufficiently advanced" property behavior provide the same static initialization guarantees (e.g. its initialization is statically known) for its stored property, and then use dispatch_once as its implementation? The compiler doesn’t know anything magic here.

-Chris

···

On May 31, 2016, at 6:20 PM, Joe Groff <jgroff@apple.com> wrote:

If the goal was to remove magic from the compiler, then a possible direction would be to do something like:

1) Introduce a new declmodifier named something like “atomiclazy”.
2) Disallow global and static variables, unless they are explicitly marked atomiclazy (compiler would provide a fixit hint to suggest this).
3) Replace the atomiclazy magic with a property behavior when they exist.

This doesn't make sense. This "magic" is the only sensible way to initialize a global that requires dynamic initialization. Even with property behaviors, we would need to lazily apply the behavior's initialization logic to get the property into its initial state. Nonatomic lazy would be a misoptimization—it's hard to beat dispatch_once at its own game.


(Joe Groff) #12

There's quite a bit of magic about global initialization—it knows it can hoist and fold initialization checks, and how to turn effectively constant initializations into static initializations and avoid the laziness altogether. dispatch_once furthermore only works for global initializations, since its token is magic and must be statically initialized to zero at process start. Since our existing global initialization implementation only works for globals, and does the right thing for 99% of globals, this seems like foolish generalization to me, punishing the common case and opening opportunity for user mistakes for no gain.

-Joe

···

On May 31, 2016, at 5:54 PM, Chris Lattner <clattner@apple.com> wrote:

On May 31, 2016, at 6:20 PM, Joe Groff <jgroff@apple.com> wrote:

If the goal was to remove magic from the compiler, then a possible direction would be to do something like:

1) Introduce a new declmodifier named something like “atomiclazy”.
2) Disallow global and static variables, unless they are explicitly marked atomiclazy (compiler would provide a fixit hint to suggest this).
3) Replace the atomiclazy magic with a property behavior when they exist.

This doesn't make sense. This "magic" is the only sensible way to initialize a global that requires dynamic initialization. Even with property behaviors, we would need to lazily apply the behavior's initialization logic to get the property into its initial state. Nonatomic lazy would be a misoptimization—it's hard to beat dispatch_once at its own game.

Why couldn’t a "sufficiently advanced" property behavior provide the same static initialization guarantees (e.g. its initialization is statically known) for its stored property, and then use dispatch_once as its implementation? The compiler doesn’t know anything magic here.