---
# System prepended metadata

title: CasePath

---

# CasePath
## 🔗 GitHub

- https://github.com/pointfreeco/swift-case-paths

## 🎬 Point・Free

- [https://www.pointfree.co/episodes/ep87-the-case-for-case-paths-introduction](https://www.pointfree.co/episodes/ep87-the-case-for-case-paths-introduction)
- [https://www.pointfree.co/episodes/ep88-the-case-for-case-paths-properties](https://www.pointfree.co/episodes/ep88-the-case-for-case-paths-properties)
- [https://www.pointfree.co/episodes/ep89-case-paths-for-free](https://www.pointfree.co/episodes/ep89-case-paths-for-free)

## Content

1. KeyPath
2. What is a KeyPath used for?
3. CasePath
4. Key points
5. Supplement

## KeyPath

> A key path from a specific root type to a specific resulting value type.
> 

我們通常會這樣寫...

```swift=
class Location {
    var city: String
    var country: String
    
    init(city: String, country: String) {
        self.city = city
        self.country = country
    }
}

struct User {
    let id: Int
    let name: String
    let age: Int
    var phone: String?
    var location: Location
    var isAdult: Bool {
        age >= 18
    }
}

let location = Location(city: "Taipei", country: "Taiwan")
let user = User(id: 1, name: "Bing", age: 28, phone: "0900000000", location: location)
user.name             // "Bing"
user.location.country // "Taiwan"
```

KeyPath 寫法會這樣寫...

```swift=
let userPath = \User.name // KeyPath<User, String>
user[keyPath: userPath]   // "Bing"             

let phonePath = \User.phone // WritableKeyPath<User, Optional<String>>
user[keyPath: phonePath]    // "0900000000"

let locationPath = \User.location.city // ReferenceWritableKeyPath<User, String> 
user[keyPath: locationPath]            // Taipei

user[keyPath: \.location.city] // type inference
```

**概念**上像這樣，有一組 getter & setter
```swift=
struct _WritableKeyPath<Root, Value> {
  let get: (Root) -> Value
  let set: (inout Root, Value) -> Void
}
```

## What is a KeyPath used for?

> They unlock the ability to do many things, like [key-value observing](https://developer.apple.com/documentation/swift/cocoa_design_patterns/using_key-value_observing_in_swift) and [reactive bindings](https://developer.apple.com/documentation/combine/receiving_and_handling_events_with_combine), [dynamic member lookup](https://github.com/apple/swift-evolution/blob/master/proposals/0252-keypath-dynamic-member-lookup.md), and scoping changes to the SwiftUI [environment](https://developer.apple.com/documentation/swiftui/environment).
> 

### 1. Point Free Style

> point free style 是 FP 的應用；我們使用各種事先定義好的 function 組合出我們要做的事情，這些 function 都不牽涉到數據的處理。
數據流與程式行為**分離**開來，使我們只關注在**做什麼**，而非**怎麼做**(如何處理數據)。 - [[Javascript] Point Free Style 如何幫助提高程式可讀性](https://medium.com/%E4%B8%80%E5%80%8B%E5%B0%8F%E5%B0%8F%E5%B7%A5%E7%A8%8B%E5%B8%AB%E7%9A%84%E9%9A%A8%E6%89%8B%E7%AD%86%E8%A8%98/javascript-point-free-style-%E5%A6%82%E4%BD%95%E5%B9%AB%E5%8A%A9%E6%8F%90%E9%AB%98%E7%A8%8B%E5%BC%8F%E5%8F%AF%E8%AE%80%E6%80%A7-fe865053a06d)
> 

```swift=
let userList = [
    User(id: 1, name: "Steven", age: 28, phone: "0912123123", location: location),
    User(id: 2, name: "Denis", age: 30, location: location),
    User(id: 3, name: "Wedy", age: 13, location: location)
]

userList.map { $0.name }
// v.s.
userList.map(\.name)

userList.filter { $0.isAdult }
// v.s.
userList.filter(\.isAdult)

userList.compactMap { $0.phone }
// v.s.
userList.compactMap(\.phone)
```

### 2. KVO

```swift=
class Person: NSObject {
    @objc dynamic var age: Int
    
    init(age: Int) {
        self.age = age
    }
    
    func growth() {
        age += 1
    }
}

let me = Person(age: 27)
me.observe(\.age, options: [.initial, .old]) { person, changed in
    print(changed.oldValue ?? "--", " > ", person.age)
}
me.growth()

/* Output
nil 27
Optional(27) 28
*/
```

### Combine

```swift=
let subject = PassthroughSubject<String, Never>()
subject.assign(to: \.text, on: label)

subject.send("HelloWorld")
label.text // "HelloWorld"
```

### KeyPath in Enum?

```swift=
enum Authentication {
    case authenticated(accessToken: String)
    case unauthenticated
}

\Authentication.unauthenticated

// ❌ Key path cannot refer to static member 'unauthenticated'
```

It’s hard to get associated value.

```swift=
let auth = Authentication.authenticated(accessToken: "aBcDE123")

// switch
switch auth {
case let .authenticated(accessToken):
    print("authenticated: \(accessToken)")
case .unauthenticated:
    break
}

// if case let
if case let Authentication.authenticated(accessToken) = auth {
    print("authenticated: \(accessToken)")
}
```

## CasePath

> Case paths bring the power and ergonomics of key paths to enums!
They unlock the ability to do many things, like [key-value observing](https://developer.apple.com/documentation/swift/cocoa_design_patterns/using_key-value_observing_in_swift) and [reactive bindings](https://developer.apple.com/documentation/combine/receiving_and_handling_events_with_combine), [dynamic member lookup](https://github.com/apple/swift-evolution/blob/master/proposals/0252-keypath-dynamic-member-lookup.md), and scoping changes to the SwiftUI [environment](https://developer.apple.com/documentation/swiftui/environment).
> 

```swift=
import CasePaths
```

**概念**上跟 KeyPath 相似，也是有一組 getter(extract) & setter(embed)
```swift=
struct CasePath<Root, Value> {
  let extract: (Root) -> Value?
  let embed: (Value) -> Root
}
```

**embed** (like getter...?)
```swift=
let _embedAuth = Authentication.authenticated(accessToken: "cafebeef")

// v.s.
let embedAuth = (/Authentication.authenticated).embed("cafebeef") // Returns a root by embedding a value.
```
**extract** (like setter...?)
```swift=
if case let Authentication.authenticated(accessToken) = _embedAuth {
    print(accessToken)
}

// v.s.
(/Authentication.authenticated).extract(from: embedAuth) // Attempts to extract a value from a root.


(/Authentication.authenticated).extract(from: .unauthenticated) // nil
```
**example 1: compactMap**
```swift=
let authentications: [Authentication] = [
    .unauthenticated,
    .authenticated(accessToken: "abc"),
    .authenticated(accessToken: "efg"),
    .unauthenticated
]

authentications.compactMap { auth -> String? in
    if case let .authenticated(accessToken) = auth {
        return accessToken
    }
    return nil
}

// v.s.
authentications.compactMap(/Authentication.authenticated)
```
**example 2: compactMap**
```swift=
observable
    .map {
        switch $0 {
        case .cancel:
            return nil
        case let .finish(user: user, auth: auth):
            return (user: user, auth: auth)
        }
    }
    .compactMap { $0 }
    .bind(to: otherObserver)
    .disposed(by: disposeBag)

// v.s.
observable
    .compactMap(/FinishReason.finish)
    .bind(to: otherObserver)
    .disposed(by: disposeBag)
```

## Key points
- **key paths**: package up the functionality of getting and setting a value on a root structure.
    - `user[keyPath: \User.phone]`
    - `user[keyPath: \User.phone] = "0912312300"`
```swift=
struct _WritableKeyPath<Root, Value> {
  let get: (Root) -> Value
  let set: (inout Root, Value) -> Void
}
```

- **case paths**: package up the functionality of extracting and embedding a value on a root enumeration.
    - `(/Authentication.authenticated).extract(from: embedAuth)`
    - `(/Authentication.authenticated).embed("cafebeef")`
```swift=
struct CasePath<Root, Value> {
  let extract: (Root) -> Value?
  let embed: (Value) -> Root
}
```

## Supplement

1. 最起初的提案 
    - [Automatically derive properties for enum cases](https://github.com/stephencelis/swift-evolution/blob/enum-case-property-derivation/proposals/0000-automatically-derive-properties-for-enum-cases.md)
2. 本來是用 Reflection 實現，因為效能關係，以 Metadata 改寫。
    - [[Pitch] Enum Quality of Life Improvements](https://forums.swift.org/t/pitch-enum-quality-of-life-improvements/52770/18)
2. 論壇有在討論是否要將 case path 納入 swift，但似乎受限於 compiler 設計，目前仍是討論中。
    - [[Discussion] Inclusion of CasePaths into the language](https://forums.swift.org/t/discussion-inclusion-of-casepaths-into-the-language/53024)