The Swift-based Enum structure belongs to the "Sum" type

Keywords: Swift network

Enum structure should be used by everyone. Let's take a brief look at the use of Swift.

The algebraic type is not a specific type, but a way of thinking about the original type. Sum type is a kind of algebraic type. Rational use of Sum type can greatly improve the readability of code.

Sum type

Enum in Swift is a typical Sum type, for example:
enum SumExample {
  case a(Bool)
  case b(Bool)
}
We can list all the cases of SumExample:
let first = SumExample.a(true)
let second = SumExample.b(true)
let third = SumExample.a(false)
let fourth = SumExample.b(false)

It can be seen that the number of possible values of Enum is the sum of the number of possible values of all components of Enum, so the result of Npv(SumExample) is Npv(Bool) + Npv(Bool) = 2 + 2 = 4.

Another example is:

enum SumExampleTwo {
  case a(Bool)
  case b(Int8)
}

Npv(SumExampleTwo) = Npv(Bool) + Npv(Int8) = 2 + 256 = 258.

How can we use this feature to write better code?

1. Use Enum as the return value:

If we define a method to send a request and return a String type result, let's take a look at the code that was common before.

typealias Handler = (String?, Error?) -> Void

func getUser(from: URL, completionHandler: Handler) {
  // function implementation
}

getUser(from: someUrl) { result, error in
  if let result = result {
    // Handle result
  }
  if let error = error {
    // Handle error
  }
}

Why is this a bad choice? Because our return value has only two possibilities:

  • success - Get results from the server
  • fail-Function Processing Errors
Seeing this code, we can make the following four judgments according to the return value when we are not familiar with the business:
result = nil, error = not nil // Case 1
result = not nil, error = nil  // Case 2
result = not nil, error = not nil // Case 3
result = nil, error = nil // Case 4

But in fact, success or failure only requires two possibilities:

Success: result!= nil, error = nil

Failure: result = nil, error!= nil

The reason for this problem is that we use the Product type instead of the Sum type.

The code that replaces the return value with enum is now like this.

enum Result {
    case success(String)
    case error(Error)
}

typealias Handler = (Result) -> Void

func getUser(from: URL, completionHandler: (Handler)) {
  // implementation
}

getUser(from: someUrl) { response in
    switch response {
    case .success(let result):
        print(result)
    case .error(let error):
        print(error.localizedDescription)
    }
}

We created a Sum type called Result, which we used to distinguish between two possibilities. Our use case is in line with our actual situation, which is very good.

2. Optional enum

Optional, the most commonly used type of Swift, is implemented internally using the Sum type Enum:

enum Optional<T> {
  case some(T)
  case none
}

So let a: String? = "Hello" is a short version of let a = Optional.some("Hello").

The good news is that Swift has some concise grammatical sugar to help us distinguish between Sum types - if let and guardlet structures.

let a: String? = "Hello"

if let a = a {
    print(a)
} else {
    print("error")
}

Amount to:

let a = Optional.some("Hello")

switch a {
case .some(let res):
  print(res)
case .none:
  print("Error")
}

3. Use Sum type to represent routing

In your application, the possibilities for something are limited and can be easily expressed in Sum type. For example, enum is used to represent different network requests:

enum Router {
    case user(id: Int)
    case weather(day: Day)
}

extension Router {
    var url: String {
        switch self {
        case .user(let id):
            return "\(App.BaseUrl)/user/\(id)"
        case .weather(let day):
            return "\(App.BaseUrl)/weather/\(day.rawValue)"
        }
    }
}

Your Router can expose everything in this way, such as parameters, request headers, request types, etc.

Now, if you replace the application theme style, you can try this way:

struct AppThemeModel {
    let baseColor: UIColor
    let backgroundColor: UIColor
    let accentColor: UIColor
    let baseFont: UIFont
}

enum AppTheme {
    case dark
    case light

    var model: AppThemeModel {
      switch self {
      case .dark:
        return AppThemeModel(
          baseColor: .red
          backgroundColor: .darkRed
          accentColor: .yellow
          baseFont: .systemFontOfSize(12)
        )
      case .light:
        AppThemeModel(
          baseColor: .white
          backgroundColor: .gray
          accentColor: .blue
          baseFont: .systemFontOfSize(13)
        )
      }
    }
}


// During app init
var currentAppTheme = AppTheme.dark

4. Implementing data structure

It is very easy to implement trees and linked lists in swift using the sum type.

indirect enum Tree<T> {
    case node(T, l: Tree, r: Tree)
    case leaf(T)

    var l: Tree? {
      switch self {
      case .node(_, l: let l, _):
        return l
      case .leaf(_):
        return nil
      }
    }

    var r: // equivalent implementation to l

    var value: T {
      switch self {
      case .node(let val, _, _):
        return val
      case .leaf(let val):
        return val
      }
    }
}

let tree = Tree.node(12, l: Tree.leaf(11),
                         r: Tree.node(34, l: Tree.leaf(34),
                                          r: Tree.leaf(55)))









Posted by Ammar on Fri, 14 Jun 2019 11:52:42 -0700