Enums not allowing stored instance properties is a design choice. Having enum with stored instance properties makes it like a struct (with enum superpowers), but just from type perspective now enums will act like type multipliers. Basically consider
enum Set1 {
case a
case b
case c
}
enum Times {
case x
case y
var k: Set1
}
What this actually mean that enum Times
allows us having any combination of elements from Set1
and Set2
resulting in 6 distinct cases, but wait, we know actually this is a purpose of a tuple type like (Set1, Set2)
, where Times
can be declared as
typealias Times = (Set1, Set2)
I hope this serves as a reasonable rationale for not allowing the former case.
That being said, swift enums allow us associating an arbitrary n-tuple with each case, thus allowing us to declare what is known as discriminated union in functional programming. Call it a stored property attached to case if you like. From types perspectives it now acts as type adder.
enum Add {
case lhs(Set1)
case rhs(Set2)
}
We now have 5 different cases. If we now store 2-tuples:
enum AddTimes {
case lhs(Set1, Set2)
case rhs(Set3, Set4)
}
we now basically have sum of multiplication (Set1 * Set2 + Set3 * Set4).
This is a very powerful tool when it comes to pattern matching.
HOWEVER, there exist some real cases when you actually want to emulate the form of stored property inside enum. Consider this:
public enum Service {
case registerNewUser(username: String, password: String, language: String)
case login(username: String, password: String, deviceTokenº: String?)
case logout(sessionToken: String)
case sendForgotPassword(email: String)
}
is a declarative way to define REST endpoints (in framework like Moya)
When you want to fire a request you would do something like
MoyaProvider<Service>.request(.sendForgotPassword(email: "foo@foo.com"))
But now imagine you want to differentiate between you production and test server. If you add a server as another tuple element in each case:
case forgotPassword(sessionToken: String, serverBaseURLString: String)
this will be have a wrong semantics, since you originally intend each tuple to store request parameters, but now it stores a server base address.
To avoid things like this we can actually parametrize our type in the following way. Instead of having Server being defined as say:
enum Server: String {
case production = "https://service.info"
case test = "http://test.service.info"
}
we can define it with distinct type for each case like:
public struct ProductionServer: ServerType {
public static var baseURLString: String { return "https://service.info" }
}
public struct TestServer: ServerType {
public static var baseURLString: String { return "http://test.service.info" }
}
public protocol ServerType {
static var baseURLString: String { get }
}
and finally parametrize our ServiceType as
public enum Service<T> where T: ServerType {
case registerNewUser(username: String, password: String, language: String)
case login(username: String, password: String, deviceTokenº: String?)
case logout(sessionToken: String)
case sendForgotPassword(email: String)
var serverURL: URL {
return T.baseURL
}
}
public typealias ProdutionService = Service<ProductionServer>
public typealias TestService = Service<TestServer>