Since some people are asking for examples where this pitch would be useful, I’ll give a real world example from a current project. Enums are great when modeling grammars of different kinds. Let’s say we’re modeling CSS properties, and we need to represent <length>
:
enum CSSLength {
case px(Double)
case em(Double)
case vh(Double)
case vw(Double)
}
But then we realize that some CSS properties take <length> | <percentage>
, some take <length> | <percentage> | auto
, and some take <length> | <percentage> | <number>
. So we represent those too:
enum CSSLength {
case px(Double)
case em(Double)
case vh(Double)
case vw(Double)
}
enum CSSLengthPercentage {
case length(CSSLength)
case percentage(Double)
}
enum CSSLengthPercentageAuto {
case length(CSSLength)
case percentage(Double)
case auto
}
enum CSSLengthPercentageNumber {
case length(CSSLength)
case percentage(Double)
case number(Double)
}
When we switch from using CSSLength
to one of the other types, we’d prefer not to change any of the call sites. Going through the entire test suite and changing .px(10)
to .length(.px(10))
would be a chore and would hurt readability.
So we define a protocol for anything that can be expressed as a <length>
:
enum CSSLength {
case px(Double)
case em(Double)
case vh(Double)
case vw(Double)
}
enum CSSLengthPercentage {
case length(CSSLength)
case percentage(Double)
}
enum CSSLengthPercentageAuto {
case length(CSSLength)
case percentage(Double)
case auto
}
enum CSSLengthPercentageNumber {
case length(CSSLength)
case percentage(Double)
case number(Double)
}
protocol CSSLengthExpressible {
init(_: CSSLength)
}
extension CSSLengthExpressible {
static func px(_ value: Double) -> Self {
.init(.px(value))
}
static func em(_ value: Double) -> Self {
.init(.em(value))
}
static func vh(_ value: Double) -> Self {
.init(.vh(value))
}
static func vw(_ value: Double) -> Self {
.init(.vw(value))
}
}
extension CSSLengthPercentage: CSSLengthExpressible {
init(_ length: CSSLength) {
self = .length(length)
}
}
extension CSSLengthPercentageAuto: CSSLengthExpressible {
init(_ length: CSSLength) {
self = .length(length)
}
}
extension CSSLengthPercentageNumber: CSSLengthExpressible {
init(_ length: CSSLength) {
self = .length(length)
}
}
That definitely works, but having to write the protocol conformance manually for each type is clearly just boilerplate. With this proposal, it would just become:
enum CSSLength {
case px(Double)
case em(Double)
case vh(Double)
case vw(Double)
}
enum CSSLengthPercentage: CSSLengthExpressible {
case length(CSSLength)
case percentage(Double)
}
enum CSSLengthPercentageAuto: CSSLengthExpressible {
case length(CSSLength)
case percentage(Double)
case auto
}
enum CSSLengthPercentageNumber: CSSLengthExpressible {
case length(CSSLength)
case percentage(Double)
case number(Double)
}
protocol CSSLengthExpressible {
static func length(_: CSSLength) -> Self
}
extension CSSLengthExpressible {
static func px(_ value: Double) -> Self {
.length(.px(value))
}
static func em(_ value: Double) -> Self {
.length(.em(value))
}
static func vh(_ value: Double) -> Self {
.length(.vh(value))
}
static func vw(_ value: Double) -> Self {
.length(.vw(value))
}
}
Fewer lines, and a much clearer expression of the programmer’s intent. And that’s just for a small part of CSS. If we were to model the entire CSS grammar, this proposal could make a huge difference, both in terms of lines saved and in terms of clarity.
In fact, I think this proposal will be broadly useful whenever you need to migrate from structs to enums, from enums to structs, or between different levels of enum nesting. The ability to use cases and static functions interchangeably makes that type of migration not just manageable, but even relatively easy. This proposal would expand the scenarios where cases and static functions can be used interchangeably, making that type of migration even easier.