lazy keyword vs lazy initialization pattern


(Cameron Knight) #1

Unless I'm missing something, this should give you the results you are looking for:

class MyClass {
    var foreignKey: Int64 {
        didSet {
            self.foreignObject = nil
        }
    }

    lazy var foreignObject: ForeignClass! = {
        return Database.expensiveSelect(self.foreignKey)
    }()
}

Using an Implicitly Unwrapped Optional instead of an Optional allows you to set foreignObject to nil which will give you the same behavior you use in Objective-C (a.k.a. null_resettable).

···

To me this actually feels more like something that might fit better as an additional type of optional, rather than a language feature.

Setting this optional to nil could work normally, and any attempt to access the value when nil would cause the reload to occur. The initialization semantics might be a tad ugly though.

var myLazyOpt = ReloadingOptional<ForeignClass>({
return Database.expensiveSelect(self.foreignKey)
})

You could probably implement something like this yourself, though I'm not sure how elegant that would be without full language support.

Thanks for your time,
Cole Kurkowski
> On Dec 4, 2015, at 07:40, David Hart <david at hartbit.com <https://lists.swift.org/mailman/listinfo/swift-evolution>> wrote:
>
> In Objective-C, I often used the lazy initialization pattern to implement a cache for expensive operations. For exemple, here is an often used scenario in a project where objects behind foreign keys in a database ORM are only fetched when necessary:
>
> @interface MyClass : NSObject
>
> @property (nonatomic) ForeignClass* foreignObject;
> @property (nonatomic) int64_t foreignKey;
>
> @end
>
> @implementation MyClass
>
> - (void)setForeignKey:(int64_t)foreignKey {
> _foreignKey = foreignKey;
> _foreignObject = nil;
> }
>
> - (ForeignClass*)foreignObject {
> if (!_foreignObject) {
> _foreignObject = [Database expensiveSelect:_foreignKey];
> }
> return _foreignObject;
> }
>
> @end
>
> Unfortunately, the lazy keyword in Swift, which was supposed to make the lazy initialization pattern more concsive does not work in this case:
>
> class MyClass {
> var foreignKey: Int64 {
> didSet {
> self.foreignObject = nil
> }
> }
>
> lazy var foreignObject: ForeignClass? = {
> return Database.expensiveSelect(self.foreignKey)
> }()
> }
>
> I'm forced to rewrite it this way:
>
> class MyClass {
> var foreignKey: Int64 {
> didSet {
> self.foreignObject = nil
> }
> }
>
> private var _foreignObject: ForeignClass? = nil
> var foreignObject: ForeignClass? {
> if _foreignObject == nil {
> _foreignObject = Database.expensiveSelect(self.foreignKey)
> }
> return _foreignObject
> }
> }
>
> When thinking about it, I came to the conclusion that the use cases of lazy seem very narrow compared to how useful the lazy initialization pattern was in Objective-C.
> I want your opinion on three alternatives:
>
> 1- Do nothing, and use the slightly uglier Swift example when using a cache.
> 2- Modify lazy semantics to re-calculates when nil (I think this is the worst solution).
> 3- Add a cache modifier that re-calcualtes when nil.
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org <https://lists.swift.org/mailman/listinfo/swift-evolution>
> https://lists.swift.org/mailman/listinfo/swift-evolution


(Lily Ballard) #2

AFAIK this behavior isn't actually documented anywhere, which means the
fact that it works at all is surprising. I'm guessing it does this
because null_resettable properties are modeled as IUOs, but the fact
that it's not documented means I'm leery of relying on it.

I'd much rather that this didn't work, but that there was some syntax to
get access to the underlying storage of the lazy property so you can set
it to nil. Or alternatively that lazy properties actually had an
overloaded setter that accepted `nil` (or just a single setter that took
an optional, but I'm not convinced that's a good idea), and then
null_resettable could be translated directly into a lazy property.
Though I still actually want access to the underlying storage because I
want to be able to test if the lazy property is already initialized
without having to make the initializer record that fact elsewhere.

-Kevin Ballard

Unless I'm missing something, this should give you the results you are
looking for:

class MyClass { var foreignKey: Int64 { didSet {
self.foreignObject = nil } }

lazy var foreignObject: ForeignClass! = {
returnDatabase.expensiveSelect(self.foreignKey) }() }

Using an Implicitly Unwrapped Optional instead of an Optional allows
you to set foreignObject to nil which will give you the same behavior
you use in Objective-C (a.k.a. null_resettable).

To me this actually feels more like something that might fit better
as an additional type of optional, rather than a language feature.

Setting this optional to nil could work normally, and any attempt to
access the value when nil would cause the reload to occur. The
initialization semantics might be a tad ugly though.

var myLazyOpt = ReloadingOptional<ForeignClass>({ return
Database.expensiveSelect(self.foreignKey) })

You could probably implement something like this yourself, though I'm
not sure how elegant that would be without full language support.

Thanks for your time, Cole Kurkowski

* On Dec 4, 2015, at 07:40, David Hart <david at hartbit.com[1]> wrote:

*>**>* In Objective-C, I often used the lazy initialization pattern to
implement a cache for expensive operations. For exemple, here is an
often used scenario in a project where objects behind foreign keys in a
database ORM are only fetched when necessary: *>**>* @interface MyClass
: NSObject *>**>* @property (nonatomic) ForeignClass* foreignObject; *>*
@property (nonatomic) int64_t foreignKey; *>**>* @end *>**>*
@implementation MyClass *>**>* - (void)setForeignKey:(int64_t)foreignKey
{ *>* _foreignKey = foreignKey; *>* _foreignObject = nil; *>* }
*>**>* - (ForeignClass*)foreignObject { *>* if (!_foreignObject) { *>*
_foreignObject = [Database expensiveSelect:_foreignKey]; *>* } *>*
return _foreignObject; *>* } *>**>* @end *>**>* Unfortunately, the lazy
keyword in Swift, which was supposed to make the lazy initialization
pattern more concsive does not work in this case: *>**>* class MyClass {
*>* var foreignKey: Int64 { *>* didSet { *>*
self.foreignObject = nil *>* } *>* } *>**>* lazy var
foreignObject: ForeignClass? = { *>* return
Database.expensiveSelect(self.foreignKey) *>* }() *>* } *>**>* I'm
forced to rewrite it this way: *>**>* class MyClass { *>* var
foreignKey: Int64 { *>* didSet { *>* self.foreignObject
= nil *>* } *>* } *>**>* private var _foreignObject:
ForeignClass? = nil *>* var foreignObject: ForeignClass? { *>*
if _foreignObject == nil { *>* _foreignObject =
Database.expensiveSelect(self.foreignKey) *>* } *>* return
_foreignObject *>* } *>* } *>**>* When thinking about it, I came to
the conclusion that the use cases of lazy seem very narrow compared to
how useful the lazy initialization pattern was in Objective-C. *>* I
want your opinion on three alternatives: *>**>* 1- Do nothing, and use
the slightly uglier Swift example when using a cache. *>* 2- Modify lazy
semantics to re-calculates when nil (I think this is the worst
solution). *>* 3- Add a cache modifier that re-calcualtes when nil. *>*
_______________________________________________ *>* swift-evolution
mailing list *>*swift-evolution at swift.org[2]*>*
https://lists.swift.org/mailman/listinfo/swift-evolution*

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

Links:

  1. https://lists.swift.org/mailman/listinfo/swift-evolution
  2. https://lists.swift.org/mailman/listinfo/swift-evolution

···

On Fri, Dec 4, 2015, at 04:33 PM, Cameron Knight wrote: