Why do enums have computed properties but not stored properties in Swift?

i0S Swift Issue

Question or problem with Swift language programming:

I am new to Swift and just came across this in the documentation:

Why is that? Do associated values for enum work like stored properties? It seems like they had stored properties initially – Why no stored type properties for classes in swift?

How to solve the problem:

Solution 1:

enums do have stored type properties – i.e., static properties. They don’t have stored instance properties. I don’t know if there is a technical reason why stored instance properties are not available for enums. You may have to ask your question on the dev forum if you want a technical answer for “why”.

In your question you ask if associated values work like stored properties. In fact, they do, and are more flexible (in some ways) than stored properties for structs and classes. Each case in an enum can have its own specialized set of data that is associated with it. Rather than have one set of stored properties that apply to all cases, you get to individualize the stored properties for each case.

Solution 2:

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.request(.sendForgotPassword(email: "[email protected]"))

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 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
public typealias TestService = Service

Solution 3:

I use a small trick to store properties that are not accesible on initialization.

First, I create a class Future which will store the stored property in the future:

class Future {
  var value: T?
  init(value: T? = nil) {
      self.value = value
  }
}

Then I create my enum:

enum Sample {
  case Test(future: Future)
}

Instantiate:

let enumTest = Sample.Test(future: Future())

Later in the code:

switch enumTest {
  case let .Test(future):
  future.value = "Foo"
}

And later on, you can access the value:

switch enumTest {
  case let .Test(future):
  // it will print Optional("Foo")
  print("\(future.value)")
}

This is not something you should abuse, but it can be helpful in some cases.

Hope it helps.

Solution 4:

An enum is considered as a structured data type that can be modified without needing to change say a String or Int multiple times within your code and with a enum we can never have to worry about changing the same thing more than once. For example dropdown menu:

enum DropDownMenuOptions: String {
  case MenuOptionOne
  case MenuOptionTwo
  case MenuOptionThree
}

With stored properties u can precalculate needed information and reduce code in your main function. Best example is calculating size of rect for example:

struct Point {
    var x = 0.0, y = 0.0
}
struct Size {
    var width = 0.0, height = 0.0
}
struct Rect {
    var origin = Point()
    var size = Size()
    var center: Point {
        get {
            let centerX = origin.x + (size.width / 2)
            let centerY = origin.y + (size.height / 2)
            return Point(x: centerX, y: centerY)
        }
        set(newCenter) {
            origin.x = newCenter.x - (size.width / 2)
            origin.y = newCenter.y - (size.height / 2)
        }
    }
}

var square = Rect(origin: Point(x: 0.0, y: 0.0),
    size: Size(width: 10.0, height: 10.0))

Hope this helps!