Swift Holy Wars: To bracket or not in option sets?

As much as I’d like OptionSets to be a type, they’re not.

public protocol OptionSet : SetAlgebra, RawRepresentable

The current implementation (via a protocol), enables API evolution. As Joe Groff pointed out to me, developers can break down a single option into multiple refined options, while still offering the original components. You can see an example of this in the following implementation, where the compound energyStar and gentleStar  options occupy an equal footing with  component bit-shifted flags:

public struct LaundryOptions: OptionSet {
    public static let lowWater = LaundryOptions(rawValue: 1 << 0)
    public static let lowHeat = LaundryOptions(rawValue: 1 << 1)
    public static let gentleCycle = LaundryOptions(rawValue: 1 << 2)
    public static let tumbleDry = LaundryOptions(rawValue: 1 << 3)
    
    public static let energyStar: LaundryOptions = [.lowWater, .lowHeat]
    public static let gentleStar: LaundryOptions = [.energyStar, .gentleCycle]
    
    public init(rawValue: Int) {
        self.rawValue = rawValue
    }
    public var rawValue: Int
}

Although this design looks like you are using sets, you really aren’t.  The square bracket syntax is a bit a of a cheat:

let options1: LaundryOptions = [.lowWater, .lowHeat]
let options2: LaundryOptions = .energyStar
let options3: LaundryOptions = [.energyStar, .lowHeat]

// prints 3 for each one
[options1, options2, options3].forEach {
    print($0.rawValue)
}

When you surround an option set with brackets, you get back an option set. This means in Swift, [.foo] is the same as .foo.

I entered into a heated style debate today with Soroush Khanlou over option sets, specifically if it were better to make a call like accessQueue.sync(flags: [.barrier]) using or omitting the brackets.

Soroush felt that omitting the brackets produces less noise. If you can compile without the brackets, why include them?

I said “use ’em”. When you’re passing flags and a static member, let the role guide your style. When the code expects an option set, make the argument look like an option set.

The brackets clearly indicate both the argument role and creates an affordance, a specific visual indication that you can expand the set by introducing more options. Without brackets, this may not be intuitively obvious to someone less familiar with option sets.

So what do you think? Brackets? No brackets? What Swift cuisine reigns supreme?

2 Comments

  • Brackets. Because an empty `OptionSet` is represented by a `[]` pair. To not use brackets when you have only 1 item is to be pointlessly inconsistent.

  • Brackets are a nice affordance, in the case where the code expects multiple discrete flags:


    washingMachine.cleanLaundry(options: [.lowWater, .lowHeat])

    However, providing convenience composite flags makes the matter more nebulous, since frequently composite flags are intended to be unique combinations of atomic flags, e.g.:


    public struct LaundryOptions: OptionSet {
    public static let lowWater = LaundryOptions(rawValue: 1 << 0)
    public static let lowHeat = LaundryOptions(rawValue: 1 << 1)
    public static let gentleCycle = LaundryOptions(rawValue: 1 << 2)
    public static let tumbleDry = LaundryOptions(rawValue: 1 << 3)

    public static let highWater = LaundryOptions(rawValue: 1 << 4)
    public static let highHeat = LaundryOptions(rawValue: 1 << 5)
    public static let normalCycle = LaundryOptions(rawValue: 1 << 6)
    public static let heatDry = LaundryOptions(rawValue: 1 << 7)

    public static let delicates: LaundryOptions = [.lowWater, .lowHeat, .gentleCycle, .tumbleDry]
    }

    washingMachine.cleanLaundry(options: [.delicates]) // Valid combination

    washingMachine.cleanLaundry(options: [.delicates, .highHeat]) // Invalid combination, since .delicates already covers heat options

    If the caller is specifying atomic flags, e.g. [.lowWater, .lowHeat] or [.lowWater], then use brackets to denote these are combinable flags. Same would go for a mixed set of atomic and composite options, e.g. [.energyStar, .tumbleDry].

    But if caller is using a convenience flag that is intended to be a roll-up of all possible options, then skip the brackets, e.g. .delicates, since adding more options would only serve to create an invalid option set.

    Brackets would then serve to denote extensible option sets versus non-extensible option sets. If in doubt about the meaning of a particular options flag, err on the side of using brackets.

Join the Discussion

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>