Phlippie Bosman
Phlippie Bosman

Reputation: 5710

OptionSet with inverted options

Say I have a set of options that I can pass to certain processes, for example

struct Options: OptionSet {
   let rawValue: Int

   static let floogled = Options(rawValue: 1 << 0)
   static let jibjabbed = Options(rawValue: 1 << 1)
}

These are passed to some functions, for example foo(options: .floogled) and bar(options: .jibjabbed).

Now, usually when I call those functions, the intention is for all the options to be enabled. So I could define the functions with default values, for example

func foo(options: Options = [.floogled, .jibjabbed]) {
   ...
}

and so on, and then just call foo(). So far so good.

However, in the exceptional cases where I don't want to call those functions with all the options enabled, the usage would be much more intuitive if I could specify which options not to enable, rather than which options remain enabled. For example, I would rather like to call foo(options: .notFloogled) than foo(options: .jibjabbed).

I can think of two ways to do this. The first way is to change the definition of the option set so that its members are defined negatively; in other words

struct OptionsAlternative: OptionSet {
    let rawValue: Int

    static let notFloogled = Options(rawValue: 1 << 0)
    static let notJibjabbed = Options(rawValue: 1 << 1)
}

The functions would then be defined with no options disabled by default; i.e.

func foo(options: OptionsAlternative = []) {
   ...
}

And I could call it like foo() for the default behaviour, or foo(options: .notFloogled) when I need to disable specific options. However, I don't want to do this because it makes the implementation of the functions unintuitive. I would have to implement the parts that take the options into account in a double-negative fashion; compare how clumsy this is:

if !options.contains(.notFloogled) {
   // Floogling is enabled. Floogle the foo.
   ...
}

as opposed to

if options.contains(.floogled) {
   // Floogling is enabled. Floogle the foo.
   ...
}

The second way is to include members to indicate the presence of options, as in the original solution, as well as convenience members to indicate the absence of options. I.e.

extension Options {
   /// Not floogled; only jibjabbed.
   static let notFloogled: Options = [.jibjabbed]

   /// Not jibjabbed; only floogled.
   static let notJibjabbed: Options = [.floogled]
}

However, this doesn't work if I want to disable multiple options (which was arguably a major reason to use an option set in the first place). For example, if I now call foo(options: [.notFloogled, .notJibjabbed]), I get an option set with both floogling and jibjabbing enabled, rather than neither.

Is there another way of doing what I'm trying to achieve while keeping the implementation and usage as intuitive as possible?

Upvotes: 3

Views: 928

Answers (3)

matt
matt

Reputation: 535140

OptionSet is a set. It has an algebra (SetAlgebra). So you don't need "inverted options"; you invert. To invert, just subtract from the "all" case, yourself.

To illustrate (using Apple's own OptionSet example):

struct ShippingOptions: OptionSet {
    let rawValue: Int
    static let nextDay    = ShippingOptions(rawValue: 1 << 0)
    static let secondDay  = ShippingOptions(rawValue: 1 << 1)
    static let priority   = ShippingOptions(rawValue: 1 << 2)
    static let standard   = ShippingOptions(rawValue: 1 << 3)
    // include common combinations, including an all case
    static let express: ShippingOptions = [.nextDay, .secondDay]
    static let all: ShippingOptions = [.express, .priority, .standard]
}

// now form the desired options using subtractions
let negatives : ShippingOptions = [.standard] // the ones you _don't_ want
let opts = ShippingOptions.all.subtracting(negatives) // subtract them from "all"!
// and pass `opts`

Upvotes: 4

ecco55
ecco55

Reputation: 31

This is a bit confusing. So, do these Options exclude each other? Because your complement .notFloogled says it is then .jibjiabbed (in the last example). But at the top you can enable both options. What makes it more complicated is that if you have both variables .floogled and .notFloogled in your struct, you could add both to the array parameter in foo() and then which option shall be handled.

To solve the problem with the complement: I guess the raw value says which option to enable in a binary code. So the 1 stands for enable and the 0 for do nothing, I assume. In the case of using the complement with .notFloogled, I would reinterpret the binary code and say 1 is notEnable and 0 do nothing. Otherwise, if you say notFloggled is jibjabbed, you have the problem you already mentioned. So you need a new interpretation for not enable a specific option. It is hard to make suggestion when you don't know how Options is handled.

Nethertheless, I would do something like this maybe

struct Options: OptionSet {
   let rawValue: Int

   static let floogled = Options(rawValue: 1 << 0)
   static let jibjabbed = Options(rawValue: 1 << 1)
}

enum OptionsComplement {
   case notFloogled
   case notJibjabbed
}

func foo(options: Options = [.floogled, .jibjabbed]) {
   ...
}

func foo(options: [OptionsComplement] = []) {
   var optionsEnabled: Options = []() //whatever type the array is
   if !options.contains(.notFloggled) {
       optionsEnabled.append(.floogled)
   }
   if !options.contains(.notJibjabbed) {
       optionsEnabled.append(.jibjabbed)
   }
   foo(options: optionsEnabled)
}

Upvotes: 0

Phlippie Bosman
Phlippie Bosman

Reputation: 5710

I suppose that if I know that I will always pass the options in the negative style (notFloogled/notJibjabbed), and the only read them in the positive style, I could:

  1. Use the negatively-defined option set:
struct Options: OptionSet {
   let rawValue: Int

   static let notFloogled = Options(rawValue: 1 << 0)
   static let notJibjabbed = Options(rawValue: 1 << 1)
}
  1. Extend it with getters (and even setters) for checking the positively-defined options:
extension Options {
   var isFloogled: Bool {
      return !self.contains(.notFloogled)
   }

   var isJibjabbed: Bool {
      return !self.contains(.notJibjabbed)
   }
}
  1. Define the functions to check the positive indicators:
func foo(options: Options = []) {
   if options.isFloogled {
      // Floogle the foo
   }
   ...
}
  1. Still call the functions with the negatively-defined options:
foo() // With all options enabled
foo(options: .notFloogled) // With floogling disabled
foo(options: [.notFloogled, .notJibjabbed]) // With floogling jibjabbing disabled

Upvotes: -1

Related Questions