How to avoid Circular References (not strong/weak references)

I'm converting some older C++ code which contains the following:

extern BinaryExpressionFactory assign;
PrimaryExpressionFactory primary( &assign );
BinaryExpressionFactory postfix( &primary );
UnaryExpressionFactory unary( &postfix );
BinaryExpressionFactory multiplicative( &unary );
BinaryExpressionFactory additive( &multiplicative );
BinaryExpressionFactory shift( &additive );
BinaryExpressionFactory relational( &shift );
BinaryExpressionFactory logical( &relational );
ConditionalExpressionFactory conditional( &logical );
BinaryExpressionFactory assign( &conditional, true );

Obviously, this is a circular reference as the first variable needs the last one for the init method.

How can I avoid this? Internally I used a (non-optional) let next: ExpressionFactory to store the references to the next level. Do I have to make this optional and assign one level at a later time? When?
(I don't care for the strong reference cycle internally, these are all singletons)

Hey @Dirk. It would help if you shared what your Swift implementation of this looks like.

Ok, thanks. I removed most of the unnecessary noise:

class ExpressionFactory {
    init(_ level: Int, _ next: ExpressionFactory) {
        _level = level
        _next = next
    }
    let _level: Int
    let _next: ExpressionFactory

    static let primary = PrimaryExpressionFactory(assign)
    static let postfix = BinaryExpressionFactory(primary)
    static let unary = UnaryExpressionFactory(postfix)
    static let multiplicative = BinaryExpressionFactory(unary)
    static let additive = BinaryExpressionFactory(multiplicative)
    static let shift = BinaryExpressionFactory(additive)
    static let relational = BinaryExpressionFactory(shift)
    static let logical = BinaryExpressionFactory(relational)
    static let conditional = ConditionalExpressionFactory(logical)
    static let assign = BinaryExpressionFactory(conditional, true)
}
class PrimaryExpressionFactory: ExpressionFactory { init(_ next: ExpressionFactory) {super.init(0, next)}}
class BinaryExpressionFactory: ExpressionFactory { init(_ next: ExpressionFactory, _ rightAssoc: Bool = false) {_rightAssoc = rightAssoc; super.init(next._level + 1, next)}; let _rightAssoc: Bool}
class UnaryExpressionFactory: ExpressionFactory { init(_ next: ExpressionFactory) {super.init(next._level + 1, next)}}
class ConditionalExpressionFactory: ExpressionFactory { init(_ next: ExpressionFactory) { super.init(next._level + 1, next)}}

You can't create a reference cycle with let properties. So here you need to initialize those variables later. But if you want to keep things working almost as they are, you could transform let _next to a computed property and instead store an enum to identify which factory singleton to use as the next one:

enum ExpressionFactoryID {
    case primary
    ...
}
init(_ level: Int, _ nextID: ExpressionFactoryID) {
    _level = level
    _nextID = nextID
}
let _nextID: ExpressionFactoryID
var _next: ExpressionFactory {
   switch _nextID {
      case .primary: return primary
      ...
   }
}
static let primary = PrimaryExpressionFactory(.assign)

A bit more code, but you don't have to do a two-step initialization sequence this way.

Thank you very much. At least this will work.
But I still don't like the solution as everything that was in one place is now in three places (enum, switch, declaration). If I can't use _next as a direct reference, I may try to use _level (which I need anyway for different reasons) as an index into an array of ExpressionFactories.

Extending on @michelf idea:

enum Expression: Int {
    case primary
    case postfix
    case unary
    case multiplicative
    case additive
    case shift
    case relational
    case logical
    case conditional
    case assign
    
    var next: Expression {
        self == .primary ? .assign : Expression(rawValue: rawValue - 1)!
    }
    
    var isRightAssociative: Bool {
        self == .assign
    }
}
1 Like

You could look up the next factory lazily like this:

class ExpressionFactory {
    init(_ level: Int, _ next: @escaping () -> ExpressionFactory) {
        _level = level
        _next = next
    }
    let _level: Int
    let _next: () -> ExpressionFactory

    static let primary = PrimaryExpressionFactory(_assign())
    static let postfix = BinaryExpressionFactory(primary)
    static let unary = UnaryExpressionFactory(postfix)
    static let multiplicative = BinaryExpressionFactory(unary)
    static let additive = BinaryExpressionFactory(multiplicative)
    static let shift = BinaryExpressionFactory(additive)
    static let relational = BinaryExpressionFactory(shift)
    static let logical = BinaryExpressionFactory(relational)
    static let conditional = ConditionalExpressionFactory(logical)
    static let assign = BinaryExpressionFactory(conditional, true)

    static func _assign() -> ExpressionFactory { assign }
}

class PrimaryExpressionFactory: ExpressionFactory { init(_ next: @escaping @autoclosure () -> ExpressionFactory) {super.init(0, next)}}
class BinaryExpressionFactory: ExpressionFactory { init(_ next: @escaping @autoclosure () -> ExpressionFactory, _ rightAssoc: Bool = false) {_rightAssoc = rightAssoc; super.init(next()._level + 1, next)}; let _rightAssoc: Bool}
class UnaryExpressionFactory: ExpressionFactory { init(_ next: @escaping @autoclosure () -> ExpressionFactory) {super.init(next()._level + 1, next)}}
class ConditionalExpressionFactory: ExpressionFactory { init(_ next: @escaping @autoclosure () -> ExpressionFactory) { super.init(next()._level + 1, next)}}

Note that I still had to refer to one of the factories indirectly to avoid the circular reference error, even though there is no true circular reference. That's why I used the _assign function instead of naming assign directly. Since each factory subclass init takes an @autoclosure, it won't actually call the _assign function until something calls primary._next().

Thank you very much. This is the best approach.