Reputation: 5710
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
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
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
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:
struct Options: OptionSet {
let rawValue: Int
static let notFloogled = Options(rawValue: 1 << 0)
static let notJibjabbed = Options(rawValue: 1 << 1)
}
extension Options {
var isFloogled: Bool {
return !self.contains(.notFloogled)
}
var isJibjabbed: Bool {
return !self.contains(.notJibjabbed)
}
}
func foo(options: Options = []) {
if options.isFloogled {
// Floogle the foo
}
...
}
foo() // With all options enabled
foo(options: .notFloogled) // With floogling disabled
foo(options: [.notFloogled, .notJibjabbed]) // With floogling jibjabbing disabled
Upvotes: -1