[swift-evolution] A native option set construct

Brent Royal-Gordon brent at architechies.com
Mon Dec 21 16:38:29 CST 2015

> One of the things that surprised me is that there still isn’t concise syntax for creating option sets, a pattern I see out in the wild a fair bit.  While Swift 2 did introduce OptionSetType for structs, it still feels somewhat obtuse and non-obvious.  It would be great if we could something like:
> options NotSoSecretVariations {
> 	case ProteinStyle,
> 	case AnimalStyle,
> 	case GrilledOnions,
> 	...
> }
> That said, I have a feeling this isn’t the first time the Swift team has come across a pitch like this.  If this really is unfeasible, it would be great and really educational to hear what the challenges are.

Personally, what I'd like to see is the concept of an option separated from that of an option *set*. Once you do that, you can define an individual option simply using an enum, and let the set handle combining the options together.

Here's what I mean. Take an Objective-C NS_OPTIONS enum:

	typedef NS_OPTIONS(NSUInteger, NSStringCompareOptions) {
	    NSCaseInsensitiveSearch = 1,
	    NSLiteralSearch = 2,		/* Exact character-by-character equivalence */
	    NSBackwardsSearch = 4,		/* Search from end of source string */
	    NSAnchoredSearch = 8,		/* Search is limited to start (or end, if NSBackwardsSearch) of source string */
	    NSNumericSearch = 64,		/* Added in 10.2; Numbers within strings are compared using numeric value, that is, Foo2.txt < Foo7.txt < Foo25.txt; only applies to compare methods, not find */
	    NSDiacriticInsensitiveSearch NS_ENUM_AVAILABLE(10_5, 2_0) = 128, /* If specified, ignores diacritics (o-umlaut == o) */
	    NSWidthInsensitiveSearch NS_ENUM_AVAILABLE(10_5, 2_0) = 256, /* If specified, ignores width differences ('a' == UFF41) */
	    NSForcedOrderingSearch NS_ENUM_AVAILABLE(10_5, 2_0) = 512, /* If specified, comparisons are forced to return either NSOrderedAscending or NSOrderedDescending if the strings are equivalent but not strictly equal, for stability when sorting (e.g. "aaa" > "AAA" with NSCaseInsensitiveSearch specified) */
	    NSRegularExpressionSearch NS_ENUM_AVAILABLE(10_7, 3_2) = 1024    /* Applies to rangeOfString:..., stringByReplacingOccurrencesOfString:..., and replaceOccurrencesOfString:... methods only; the search string is treated as an ICU-compatible regular expression; if set, no other options can apply except NSCaseInsensitiveSearch and NSAnchoredSearch */

I think this should be translated to Swift like this:

	enum NSStringCompareOption: Int {
	    case CaseInsensitiveSearch = 0	// Because 1 << 0 == 1
	    case LiteralSearch = 1	// 1 << 1 == 2
	    case BackwardsSearch = 2
	    case AnchoredSearch = 3
	    case NumericSearch = 6	// 1 << 6 == 64
	    case DiacriticInsensitiveSearch = 7
	    case WidthInsensitiveSearch = 8
	    case ForcedOrderingSearch = 9
	    case RegularExpressionSearch = 10

Meanwhile, a method taking NSStringCompareOptions like this:

	- (NSComparisonResult)compare:(NSString *)string options:(NSStringCompareOptions)mask;

Should be translated as:

	func compare(string: NSString, options mask: OptionSet<NSStringCompareOption>) -> NSComparisonResult

OptionSet would be a generic type like this:

	struct OptionSet<Option: RawRepresentable where Option.RawValue: IntegerType>: SetAlgebraType, RawRepresentable {
	    typealias Element = Option
	    typealias RawValue = Option.RawValue

This definition should work for any NS_OPTIONS where the options are non-overlapping. I'm not sure if that's *every* option set in the Apple frameworks, but I think it's the vast majority of them. (The incompatible ones could use the existing mechanism.) And to create your own option set, all you have to do is write an enum with an integer raw type that has less than 32 or 64 cases, depending on your target. You don't even have to specify the raw values—the default ones will work fine.

Brent Royal-Gordon

More information about the swift-evolution mailing list