Coder Social home page Coder Social logo

butterflynetwork / swift-case-paths Goto Github PK

View Code? Open in Web Editor NEW

This project forked from adiayyakad/swift-case-paths

0.0 1.0 0.0 34 KB

๐Ÿงฐ Case paths bring the power and ergonomics of key paths to enums!

Home Page: https://www.pointfree.co/episodes/ep87-the-case-for-case-paths-introduction

License: MIT License

Makefile 1.31% Swift 95.39% Ruby 3.30%

swift-case-paths's Introduction

๐Ÿงฐ CasePaths

Swift 5.3 Swift 5.2 CI @pointfreeco

Case paths bring the power and ergonomics of key paths to enums!

Motivation

Swift endows every struct and class property with a key path.

struct User {
  var id: Int
  var name: String
}

\User.id   // WritableKeyPath<User, Int>
\User.name // WritableKeyPath<User, String>

This is compiler-generated code that can be used to abstractly zoom in on part of a structure, inspect and even change it, while propagating these changes to the structure's whole. They unlock the ability to do many things, like key-value observing and reactive bindings, dynamic member lookup, and scoping changes to the SwiftUI environment.

Unfortunately, no such structure exists for enum cases!

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

\Authentication.authenticated // ๐Ÿ›‘

And so it's impossible to write similar generic algorithms that can zoom in on a particular enum case.

Introducing: case paths

This library intends to bridge this gap by introducing what we call "case paths." Case paths can be constructed simply by prepending the enum type and case name with a forward slash:

import CasePaths

/Authentication.authenticated // CasePath<Authentication, String>

Case paths vs. key paths

While key paths package up the functionality of getting and setting a value on a root structure, case paths package up the functionality of extracting and embedding a value on a root enumeration.

user[keyPath: \User.id] = 42
user[keyPath: \User.id] // 42

let authentication = (/Authentication.authenticated).embed("cafebeef")
(/Authentication.authenticated).extract(from: authentication) // Optional("cafebeef")

Case path extraction can fail and return nil because the cases may not match up.

(/Authentication.authenticated).extract(from: .unauthenticated) // nil

Case paths, like key paths, compose. Where key paths use dot-syntax to dive deeper into a structure, case paths use a double-dot syntax:

\HighScore.user.name
// WritableKeyPath<HighScore, String>

/Result<Authentication, Error>..Authentication.authenticated
// CasePath<Result<Authentication, Error>, String>

Case paths, also like key paths, provide an "identity" path, which is useful for interacting with APIs that use key paths and case paths but you want to work with entire structure.

\User.self           // WritableKeyPath<User, User>
/Authentication.self // CasePath<Authentication, Authentication>

Key paths are created for every property, even computed ones, so what is the equivalent for case paths? Well, "computed" case paths can be created by providing custom embed and extract functions:

CasePath<Authentication, String>(
  embed: { decryptedToken in
    Authentication.authenticated(token: encrypt(decryptedToken))
  },
  extract: { authentication in
    guard
      case let .authenticated(encryptedToken) = authentication,
      let decryptedToken = decrypt(token)
      else { return nil }
    return decryptedToken
  }
)

Since Swift 5.2, key path expressions can be passed directly to methods like map. The same is true of case path expressions, which can be passed to methods like compactMap:

users.map(\User.name)
authentications.compactMap(/Authentication.authenticated)

Ergonomic associated value access

CasePaths uses Swift reflection to automatically and extract associated values from any enum in a single, short expression. This helpful utility is made available as a public module function that can be used in your own libraries and apps:

extract(case: Authentication.authenticated, from: .authenticated("cafebeef"))
// Optional("cafebeef")

Case paths without operators

The operators included with CasePaths make working with case paths feel a lot like working with key paths, but if your team or code base is operator-averse, they are not required.

// With operators:
/Authentication.authenticated
// Without:
CasePath.case(Authentication.authenticated)

// With operators:
authentications.compactMap(/Authentication.authenticated)
// Without:
authentications.compactMap(extract(Authentication.authenticated))

// With operators:
/Result<Authentication, Error>.success..Authentication.authenticated
// Without:
CasePath.case(Result<Authentication, Error>.success)
  .appending(path: .case(Authentication.authenticated))

// With operators:
/Authentication.self
// Without operators:
CasePath<Authentication, Authentication>.self

Installation

You can add CasePaths to an Xcode project by adding it as a package dependency.

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

If you want to use CasePaths in a SwiftPM project, it's as simple as adding a dependencies clause to your Package.swift:

dependencies: [
  .package(url: "https://github.com/pointfreeco/swift-case-paths.git", from: "0.1.2")
]

Documentation

The latest documentation for CasePaths' APIs is available here.

Other libraries

  • EnumKit is a protocol-oriented, reflection-based solution to ergonomic enum access and inspired the creation of this library.

Interested in learning more?

These concepts (and more) are explored thoroughly in Point-Free, a video series exploring functional programming and Swift hosted by Brandon Williams and Stephen Celis.

The design of this library was explored in the following Point-Free episodes:

video poster image

License

All modules are released under the MIT license. See LICENSE for details.

swift-case-paths's People

Contributors

davidcairns avatar heiberg avatar mbrandonw avatar mtfum avatar stephencelis avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.