• This repository has been archived on 18/Aug/2021
  • Stars
    star
    198
  • Rank 190,001 (Top 4 %)
  • Language
    Swift
  • License
    MIT License
  • Created about 5 years ago
  • Updated over 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

๐Ÿค Struct and enum data access in harmony.

Enum Properties has been deprecated in favor of Case Paths

Case Paths offers all of the power and functionality of enum properties, and more, without the fussiness of code generation.

This project has been archived and will remain in an unmaintained state.


๐Ÿค swift-enum-properties

Swift 5.1 @pointfreeco

Struct and enum data access in harmony.

Motivation

In Swift, struct data access is far more ergonomic than enum data access by default.

A struct field can be accessed in less than a single line using expressive dot-syntax:

user.name

An enum's associated value requires as many as seven lines to bring it into the current scope:

let optionalValue: String?
if case let .success(value) = result {
  optionalValue = value
} else {
  optionalValue = nil
}
optionalValue

That's a lot of boilerplate getting in the way of what we care about: getting at the value of a success.

This difference is also noticeable when working with higher-order functions like map and compactMap.

An array of struct values can be transformed succinctly in a single expression:

users.map { $0.name }

But transforming an array of enum values requires a version of the following incantation:

results.compactMap { result -> String? in
  guard case let .success(value) = result else { return nil }
  return value
}

The imperative nature of unwrapping an associated value spills over multiple lines, which requires us to give Swift an explicit return type, name our closure argument, and provide two explicit returns.

Solution

We can recover all of the ergonomics of struct data access for enums by defining "enum properties": computed properties that optionally return a value when the case matches:

extension Result {
  var success: Success? {
    guard case let .success(value) = self else { return nil }
    return value
  }
  
  var failure: Failure? {
    guard case let .failure(value) = self else { return nil }
    return value
  }
}

This is work we are used to doing in an ad hoc way throughout our code bases, but we can centralize it in a computed property and are free to access underlying data in a succinct fashion:

// Optionally-chain into a successful result.
result.success?.count

// Collect a bunch of successful values.
results.compactMap { $0.success }

By defining a computed property, we bridge another gap: our enums now have key paths!

\Result<String, Error>.success
// KeyPath<Result<String, Error>, String?>

Despite these benefits, defining enum properties from scratch is a tall ask. Instead, enter generate-enum-properties.

Usage

usage: generate-enum-properties [--help|-h] [--dry-run|-n] [<file>...]

    -h, --help
        Print this message.

    -n, --dry-run
        Don't update files in place. Print to stdout instead.

    --version
        Print the version.

Once installed, you can invoke generate-enum-properties from the command line and feed it any number of Swift source files:

# Insert enum properties into every enum declaration. 
$ generate-enum-properties **/*.swift

It will automatically generate and inline enum properties for every enum with associated values. Please note that it updates source files in place. Use version control to avoid accidental insertions! You can use the --dry-run flag to preview the updated source.

$ generate-enum-properties --dry-run **/*.swift

Without the --dry-run flag, the following source file as input:

enum Validated<Valid, Invalid> {
  case valid(Valid)
  case invalid(Invalid)
}

Will have its contents replaced with the following output:

enum Validated<Valid, Invalid> {
  case valid(Valid)
  case invalid(Invalid)

  var valid: Valid? {
    get {
      guard case let .valid(value) = self else { return nil }
      return value
    }
    set {
      guard case .valid = self, let newValue = newValue else { return }
      self = .valid(newValue)
    }
  }
}

Note that both a setter and getter are generated, which means you can also optionally dive into enum data and update a part of it.

validatedUser.valid?.name = "Blob"

Running generate-enum-properties is idempotent: it will only insert properties that aren't already defined in the enum declaration. One caveat:

โš ๏ธ If you have defined an enum property of the same name in an extension, it will collide with the one generated by generate-enum-properties.

Now you may be wondering: why not generate extensions that can be hidden away in another file? Unfortunately, this is problematic for enums that depend on types that need to be imported and types that are nested. By inlining enum properties, we can ensure that every associated value's type is in scope.

Xcode Code Snippets

If you or your team are not yet ready to use code generation in your code base, don't let that stop you from using enum properties! They are too useful to give up. Instead you can use our Xcode code snippet with a little bit of manual work to allow easy creation of enum properties in your code base:

Sep-25-2019 09-45-56

To install just add all of the code snippets in the .xcode directory to the following directory:

~/Library/Developer/Xcode/UserData/CodeSnippets/

and restart Xcode.

Or run the following command from the root of the repository:

$ make snippets

For more information about Xcode code snippets check out this informative NSHipster article.

Installation

Homebrew

You can install generate-enum-properties using our custom tap:

$ brew install pointfreeco/swift/generate-enum-properties
$ generate-enum-properties

SwiftPM

As a dependency

If you want to use generate-enum-properties in a project that uses SwiftPM, it's as simple as adding a dependencies clause to your Package.swift:

dependencies: [
  .package(url: "https://github.com/pointfreeco/swift-enum-properties.git", from: "0.1.0")
]

And invoking swift run from the command line:

$ swift run generate-enum-properties

As a CLI

If you want to run generate-enum-properties using SwiftPM, it's as simple as cloning the repository and invoking swift run:

$ git clone https://github.com/pointfreeco/swift-enum-properties.git
$ cd swift-enum-properties
$ swift run generate-enum-properties

Make

If you want to build and install generate-enum-properties yourself:

$ git clone https://github.com/pointfreeco/swift-enum-properties.git
$ cd swift-enum-properties
$ make install

Mint

If you want to install with Mint:

$ mint install pointfreeco/swift-enum-properties

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.

More Repositories

1

swift-composable-architecture

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.
Swift
10,990
star
2

swift-snapshot-testing

๐Ÿ“ธ Delightful Swift snapshot testing.
Swift
3,559
star
3

isowords

Open source game built in SwiftUI and the Composable Architecture.
Swift
2,515
star
4

swiftui-navigation

Tools for making SwiftUI navigation simpler, more ergonomic and more precise.
Swift
1,654
star
5

swift-dependencies

A dependency management library inspired by SwiftUI's "environment."
Swift
1,322
star
6

swift-tagged

๐Ÿท A wrapper type for safer, expressive code.
Swift
1,289
star
7

swift-overture

๐ŸŽผ A library for function composition.
Swift
1,115
star
8

pointfreeco

๐ŸŽฌ The source for www.pointfree.co, a video series on functional programming and the Swift programming language.
Swift
1,054
star
9

episode-code-samples

๐Ÿ’พ Point-Free episode code.
Swift
922
star
10

swift-case-paths

๐Ÿงฐ Case paths extends the key path hierarchy to enum cases.
Swift
852
star
11

swift-nonempty

๐ŸŽ A compile-time guarantee that a collection contains a value.
Swift
817
star
12

swift-parsing

A library for turning nebulous data into well-structured data, with a focus on composition, performance, generality, and ergonomics.
Swift
802
star
13

swift-custom-dump

A collection of tools for debugging, diffing, and testing your application's data structures.
Swift
766
star
14

swift-html

๐Ÿ—บ A Swift DSL for type-safe, extensible, and transformable HTML documents.
Swift
723
star
15

combine-schedulers

โฐ A few schedulers that make working with Combine more testable and more versatile.
Swift
671
star
16

swift-web

๐Ÿ•ธ A collection of Swift server-side frameworks for handling HTML, CSS, routing and middleware.
Swift
477
star
17

swift-identified-collections

A library of data structures for working with collections of identifiable elements in an ergonomic, performant way.
Swift
476
star
18

swift-prelude

๐ŸŽถ A collection of types and functions that enhance the Swift language.
Swift
456
star
19

swift-perception

Observable tools, backported.
Swift
399
star
20

swift-validated

๐Ÿ›‚ A result type that accumulates multiple errors.
Swift
389
star
21

swift-url-routing

A bidirectional router with more type safety and less fuss.
Swift
321
star
22

swift-concurrency-extras

Useful, testable Swift concurrency.
Swift
267
star
23

swift-gen

๐ŸŽฑ Composable, transformable, controllable randomness.
Swift
261
star
24

swift-clocks

โฐ A few clocks that make working with Swift concurrency more testable and more versatile.
Swift
229
star
25

xctest-dynamic-overlay

Define XCTest assertion helpers directly in your application and library code.
Swift
197
star
26

swift-macro-testing

Magical testing tools for Swift macros.
Swift
192
star
27

syncups

A rebuild of Appleโ€™s โ€œScrumdingerโ€ application using modern, best practices for SwiftUI development.
Swift
153
star
28

composable-core-location

A library that bridges the Composable Architecture and Core Location.
Swift
100
star
29

vapor-routing

A bidirectional Vapor router with more type safety and less fuss.
Swift
84
star
30

swift-html-vapor

๐Ÿ’ง Vapor plugin for type-safe, transformable HTML views.
Swift
82
star
31

swift-playground-templates

๐Ÿซ A collection of helpful Xcode playground templates.
Makefile
80
star
32

pointfreeco-server

Point-Free server code.
39
star
33

swift-boundaries

๐Ÿฃ Functional core, imperative shell.
Swift
27
star
34

composable-core-motion

A library that bridges the Composable Architecture and Core Motion.
Swift
26
star
35

swift-quickcheck

๐Ÿ An implementation of QuickCheck in Swift.
Swift
24
star
36

swift-algebras

Algebraic laws bundled into concrete data types.
19
star
37

swift-either

For those times you want A or B!
Swift
19
star
38

swift-parser-printer

โ†”๏ธ Parsing and printing
Swift
14
star
39

swift-html-kitura

โ˜๏ธ Kitura plugin for type-safe, transformable HTML views.
Swift
13
star
40

homebrew-swift

Ruby
2
star
41

swift-bugs

2
star
42

Ccmark

Swift
1
star