Swiftpack.co - Package - pointfreeco/swift-tagged

🏷 Tagged

MacOS CircleCI Linux Build Status

A library for safer types.

Introduction

We often work with types that are far too general or hold far too many values than what is necessary for our domain. Sometimes we just want to differentiate between two seemingly equivalent values at the type level.

For example, an email address is nothing but a String, but it should be restricted in the ways in which it can be used. And while a User id may be represented with an Int, it should be distinguishable from an Int-based Subscription id.

Examples

Tagged lets us wrap basic types in more specific contexts with ease. Given the following JSON representing a user:

{
  "id": 1,
  "email": "blob@pointfree.co",
  "subscriptionId": 1
}

We may be tempted to model it simply:

struct User: Decodable {
  let id: Int
  let email: String
  let subscriptionId: Int?
}

struct Subscription: Decodable {
  let id: Int
}

It's easy to model user and subscription ids using the same type, but our app logic shouldn't treat these values interchangeably: doing so could lead to runtime bugs and security issues!

Luckily, we can use Tagged to differentiate these types.

import Tagged

struct User: Decodable {
  typealias Id = Tagged<User, Int>

  let id: Id
  let email: String
  let subscriptionId: Subscription.Id?
}

struct Subscription: Decodable {
  typealias Id = Tagged<Subscription, Int>

  let id: Id
}

Now there's no chance we'll accidentally pass a User id where we expect a Subscription id.

Tagged depends on a generic "tag" parameter to make each type unique. In these cases, we use the parent type to tag each Id type.

What if there's no parent type? Let's say we want to constrain an Email type that wraps a string.

typealias Email = Tagged<_, String>

We can tag Email by defining an uninhabited EmailTag enum.

enum EmailTag {}
typealias Email = Tagged<EmailTag, String>

Now our User can be even more type-safe.

struct User: Decodable {
  typealias Id = Tagged<User, Int>

  let id: Id
  let email: Email
  let subscriptionId: Subscription.Id?
}

Tagged relies on conditional conformance to be practical: if the raw values are encodable or decodable, equatable, hashable, or comparable, the tagged values follow suit.

Tagged types also inherit any literal expressibility. Our User type above can be instantiated simply using the underlying literals.

User(
  id: 1,
  email: "blob@pointfree.co",
  subscriptionId: 1
)

Installation

Carthage

If you use Carthage, you can add the following dependency to your Cartfile:

github "pointfreeco/swift-tagged" ~> 0.1

CocoaPods

If your project uses CocoaPods, just add the following to your Podfile:

pod 'Tagged', '~> 0.1'

SwiftPM

If you want to use Tagged 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-tagged.git", from: "0.1.0")
]

Xcode Sub-project

Submodule, clone, or download Tagged, and drag Tagged.xcodeproj into your project.

Interested in learning more?

These concepts (and more) are explored thoroughly in Point-Free.

License

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

Github

link
Stars: 165
Help us keep the lights on

Dependencies

Used By

Total: 1

Releases

0.1.0 - Apr 16, 2018

This preliminary release has the basic Tagged type implemented with a few starting conformances to common Swift protocols.