Initializing constant object graph with cycles


(Anton Mironov) #1

Hi all,

I want to initialize constant object graph with cycles. I've considered two workarounds, but this is not a way I want it to be.

Here is an example:

// I have a context
protocol Context : class {
  /* some */
}

// I have an object that has sense only in context
class ObjectInContext {
  private weak var context: Context?
  
  init(context: Context) {
    self.context = context
  }
}

// This is what I want to do
// The object graph has a cycle, but there is no a retain cycle
class ContextA : Context {
  let object: ObjectInContext
  
  init() {
    self.object = ObjectInContext(context: self) // this code will not compile for many good reasons
  }
}

// This is workaround #1
// It looks bad for 2 reasons: implicitly unwrapped optional, it is easy to forget to initialize object
class ContextB : Context {
  var object: ObjectInContext!
  
  init() {
    self.object = ObjectInContext(context: self)
  }
}

// This is workaround #2
// It looks bad because it is even easier to forget to initialize object in init
class ContextC : Context {
  lazy var object: ObjectInContext = ObjectInContext(context: self)
  
  init() {
    let _ = self.object // lazy is not atomic so I rather initialize it here
  }
}

Does anyone have any ideas how can I do this without workarounds?

Thanks,
Anton Mironov


(Brent Royal-Gordon) #2

(Crossposted to swift-users; swift-dev is for development of the Swift compiler and standard library, not discussions about how to use Swift.)

// This is workaround #1
// It looks bad for 2 reasons: implicitly unwrapped optional, it is easy to forget to initialize object
class ContextB : Context {
var object: ObjectInContext!

init() {
   self.object = ObjectInContext(context: self)
}
}

Try this:

  class ContextB: Context {
    private var _object: ObjectInContext?
    var object: ObjectInContext { return _object! }
    
    init() {
      _object = nil
      // Note that self is now fully initialized
      _object = ObjectInContext(context: self)
    }
  }

As long as you can trust yourself not to forget to initialize `_object` within `init()` or mutate `_object` within private scope, this is about as safe as anything you could hope for.

···

On Nov 4, 2016, at 2:57 AM, Anton Mironov via swift-dev <swift-dev@swift.org> wrote:

--
Brent Royal-Gordon
Architechies


(Anton Mironov) #3

This workaround looks better, but it is still workaround.
I can trust myself this month and month after that. But I will look at this code after a while (or someone else will look at it) and will not remember/know that there has to be some initializations.
I am building tools that handle some common cases of synchronization and etc. These tools must be robust and beautiful so I can advice anyone to use them. None will adopt these tools if they introduce some new points of failure.

···

On Nov 4, 2016, at 1:35 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

(Crossposted to swift-users; swift-dev is for development of the Swift compiler and standard library, not discussions about how to use Swift.)

On Nov 4, 2016, at 2:57 AM, Anton Mironov via swift-dev <swift-dev@swift.org> wrote:

// This is workaround #1
// It looks bad for 2 reasons: implicitly unwrapped optional, it is easy to forget to initialize object
class ContextB : Context {
var object: ObjectInContext!

init() {
  self.object = ObjectInContext(context: self)
}
}

Try this:

  class ContextB: Context {
    private var _object: ObjectInContext?
    var object: ObjectInContext { return _object! }
    
    init() {
      _object = nil
      // Note that self is now fully initialized
      _object = ObjectInContext(context: self)
    }
  }

As long as you can trust yourself not to forget to initialize `_object` within `init()` or mutate `_object` within private scope, this is about as safe as anything you could hope for.

--
Brent Royal-Gordon
Architechies


(Greg Parker) #4

The IUO is the typical pattern here.

Forgetting to initialize an IUO is less of a problem than it would be in C or ObjC. Access to an IUO is checked at runtime. If you forget to initialize self.object then the process will deliberately halt the first time you try to use it.

···

On Nov 4, 2016, at 2:57 AM, Anton Mironov via swift-dev <swift-dev@swift.org> wrote:

Hi all,

I want to initialize constant object graph with cycles. I've considered two workarounds, but this is not a way I want it to be.

Here is an example:
```
// I have a context
protocol Context : class {
/* some */
}

// I have an object that has sense only in context
class ObjectInContext {
private weak var context: Context?

init(context: Context) {
   self.context = context
}
}

// This is what I want to do
// The object graph has a cycle, but there is no a retain cycle
class ContextA : Context {
let object: ObjectInContext

init() {
   self.object = ObjectInContext(context: self) // this code will not compile for many good reasons
}
}

// This is workaround #1
// It looks bad for 2 reasons: implicitly unwrapped optional, it is easy to forget to initialize object
class ContextB : Context {
var object: ObjectInContext!

init() {
   self.object = ObjectInContext(context: self)
}
}

--
Greg Parker gparker@apple.com <mailto:gparker@apple.com> Runtime Wrangler


(Dave Abrahams) #5

I hate to bring this up, but have you considered representing your graph
with value types?

···

on Fri Nov 04 2016, Anton Mironov <swift-users-AT-swift.org> wrote:

This workaround looks better, but it is still workaround.
I can trust myself this month and month after that. But I will look at
this code after a while (or someone else will look at it) and will not
remember/know that there has to be some initializations.
I am building tools that handle some common cases of synchronization
and etc. These tools must be robust and beautiful so I can advice
anyone to use them. None will adopt these tools if they introduce some
new points of failure.

--
-Dave


(Anton Mironov) #6

I agree that IUO is the best solution for now.
Sometimes I search though Scala to find the best way of doing things. They've came up with lazy as the best practice for such cases.

I’ve found (SR-1042 Make "lazy var" threadsafe)[https://bugs.swift.org/browse/SR-1042]. Implementing this will help. But I am not sure if it will be implemented any time soon.

Thanks,
Anton Mironov

···

On Nov 8, 2016, at 4:17 AM, Greg Parker <gparker@apple.com> wrote:

On Nov 4, 2016, at 2:57 AM, Anton Mironov via swift-dev <swift-dev@swift.org <mailto:swift-dev@swift.org>> wrote:

Hi all,

I want to initialize constant object graph with cycles. I've considered two workarounds, but this is not a way I want it to be.

Here is an example:
```
// I have a context
protocol Context : class {
/* some */
}

// I have an object that has sense only in context
class ObjectInContext {
private weak var context: Context?

init(context: Context) {
   self.context = context
}
}

// This is what I want to do
// The object graph has a cycle, but there is no a retain cycle
class ContextA : Context {
let object: ObjectInContext

init() {
   self.object = ObjectInContext(context: self) // this code will not compile for many good reasons
}
}

// This is workaround #1
// It looks bad for 2 reasons: implicitly unwrapped optional, it is easy to forget to initialize object
class ContextB : Context {
var object: ObjectInContext!

init() {
   self.object = ObjectInContext(context: self)
}
}

The IUO is the typical pattern here.

Forgetting to initialize an IUO is less of a problem than it would be in C or ObjC. Access to an IUO is checked at runtime. If you forget to initialize self.object then the process will deliberately halt the first time you try to use it.

--
Greg Parker gparker@apple.com <mailto:gparker@apple.com> Runtime Wrangler