[Swift] Extend @lazy to allow for optional initialization and support local variables

Originator:martijn
Number:rdar://17272417 Date Originated:6/11/2014
Status:Open Resolved:
Product:Developer Tools Product Version:
Classification: Reproducible:
 
Instance properties support @lazy to avoid immediate initialization, but a problem with this is that there is no way to suppress executing the initialization expression by supplying another value for the property. And yet, cases where we want to either manually initialize a property or use a default initialization expression are common.

If you look at the Earthquakes sample code for instance (https://developer.apple.com/library/prerelease/mac/samplecode/Earthquakes/Introduction/Intro.html#//apple_ref/doc/uid/TP40014547), properties like persistentStoreCoordinator and storeURL are implemented by combining a backing stored property (prefixed with an underscore) with a computed property and manually checking whether the backing property has been initialized. It has to resort to this because it still wants to allow setting a non-default value, and initialization expressions for properties are always executed.

One alternative that occurred to me would be to allow specifying a default block to a property definition instead of an initialization expression:

var someValue: String {
  default {
    return ...
  }
}

(Maybe don't require a return if there is only one line, similar to closures.)

The default block would only be executed if a value hadn't been set before the first access. This could work both for properties and local variables I think. Of course, this would imply lazy initialization, so supporting both might be redundant.

This also avoids having to use an explicit closure and calling it immediately after, which also seems like a pretty common pattern if initialization requires complex logic like branching:

@lazy var someValue: String = {
  switch ... {
    case [...]:
      return ...
    case [...]:
      return ...
  }
}()

Within functions/methods, @lazy is not supported at all right now. So it seems we would have to use an explicit nested function and backing variable to lazily initialize a variable:

  func doSomething() {    
    var possibleValue: String?

    func lazilyGenerateSomeValue() -> String {
      if (!possibleValue) {
        possibleValue = ...
      }
      return possibleValue!
    }

    ... lazilyGenerateSomeValue()
    ... lazilyGenerateSomeValue()
  }
  
I would want to suggest allowing @lazy here as well:
  
  func doSomething() {    
    @lazy var someValue: String = {
      return ...
    }()

    ... someValue
    ... someValue
  }
  
Or/and if default blocks are supported:
     
    var someValue: String {
      default {
        return ...
      }
    }
  
This avoids boilerplate and also keeps access uniform, so we don't have to remember to call a function instead of accessing a property to get a lazily initialized value.

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!