[swift-evolution] [swift-dev] Re-pitch: Deriving collections of enum cases

Brent Royal-Gordon brent at architechies.com
Sat Nov 11 00:15:15 CST 2017


> Personally I like the flexibility provided by the associatedtype, but I also recognize it won't be incredibly useful for enums — more so if we wanted to provide e.g. UInt8.allValues, whose ideal implementation might be "return 0...UInt8.max". So I could see allowing allValues to be any sequence or collection, but flexibility for allCases might be less important. Others should weigh in here.


I think we should allow any `Collection` (option 3), and I think we should have a `DefaultCaseCollection<Enum>` type in the standard library which encapsulates the interaction with the runtime.

A `Collection` is good because `Sequence` doesn't provide all of the guarantees we want—`allValues` should never be single-pass and it should always be possible to resume iteration at an earlier point, which `Sequence` doesn't guarantee. (It probably makes sense to use `BidirectionalCollection`, actually; I'm less sure about `RandomAccessCollection`.) At the same time, an `Array` ties us to all sorts of things that are unnecessary at best and harmful at worst, like a heap allocation. It also forces us into integer indices, which may be suboptimal for certain use cases (particularly if we later want to support associated values). And it prevents us from making types like integers conform, which I think would be a good idea.

Meanwhile, encapsulating the runtime machinery in `DefaultCaseCollection` gives users an escape hatch: If the enum you want to use doesn't conform to `ValueEnumerable`, but you're certain it's compatible, you can construct a `DefaultCaseCollection` for it. `DefaultCaseCollection` can be a `RandomAccessCollection` with `Int` indices, making it convenient to use, but at the same time, it's *not* an array, so it doesn't have to allocate storage or think about `NSArray` bridging. And it minimizes the complexity of what the compiler needs to synthesize.

	public protocol ValueEnumerable {
		associatedtype AllValues: BidirectionalCollection where AllValues.Element == Self
		static var allValues: AllValues { get }
	}

	// The compiler automatically does `typealias AllValues = DefaultCaseCollection<Self>` if the 
	// conformance is on the original declaration, the type is compatible (e.g. no associated values), 
	// and a different type is neither explicitly specified nor inferred. That will cause this default 
	// implementation to be used:
	extension ValueEnumerable where AllValues == DefaultCaseCollection<Self> {
		public static var allValues: DefaultCaseCollection<Self> {
			return DefaultCaseCollection(unsafeForEnum: Self.self)
		}
	}

	public struct DefaultCaseCollection<Enum>: RandomAccessCollection {
		public var startIndex: Int { return 0 }
		public let endIndex: Int
		
		public init(unsafeForEnum _: Enum.Type) {
			endIndex = _countCaseValues(Enum.self)
		}
		
		public subscript(i: Int) -> Enum {
			precondition(indices.contains(i), "Case index out of range")
			return Builtin.reinterpretCast(i) as Enum
		}
	}

-- 
Brent Royal-Gordon
Architechies



More information about the swift-evolution mailing list