Equatable wrapper type and a set of basic comparators.
Swift strongly encourages Equatable, and for good reason.
But in practice, equality often disappears at API boundaries:
- values stored as
any - errors erased to
Swift.Error - closures, reference types, or foreign types
- generic code that cannot add
Equatableconstraints
A very common example of this appears when using TCA, though the issue is by no means specific to it.
In TCA it’s idiomatic to make Actions equatable for better testing, diffing,
and debugging. But the moment you want to carry an error, you hit a wall:
enum Action: Equatable {
case requestFailed(Error) // ❌ 'Error' does not conform to 'Equatable'
}A typical workaround is to introduce a bespoke wrapper type that forces
equatability by comparing something like localizedDescription:
struct EquatableError: LocalizedError, Equatable {
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.localizedDescription == rhs.localizedDescription
}
let underlyingError: Error
var localizedDescription: String {
underlyingError.localizedDescription
}
}
enum FeatureAction: Equatable {
case requestFailed(EquatableError) // ✅ compiles
}This works, but it comes with tradeoffs:
- a new wrapper type must be declared
- an equality strategy must be chosen and documented
- the same pattern is often repeated across features and modules
And the “right” definition of equality is frequently context-dependent.
Equated lets you keep actions equatable without defining custom wrapper types:
import Equated
enum FeatureAction: Equatable {
case requestFailed(Equated<Error>) // ✅ compiles
}For errors, it’s enough to simply wrap the value:
return .send(.requestFailed(Equated(error)))By default, Equated chooses an appropriate equality strategy:
- if the underlying error can be compared using
Equatable,==is used - otherwise it falls back to comparing
localizedDescription
Note
Comparing localizedDescription is a heuristic. It is common, but not guaranteed to be unique or stable across localization changes.
Equality can always be customized explicitly when needed:
return .send(.requestFailed(Equated(error, by: .property(\.code))))Equated is a lightweight Equatable container that lets you define equality explicitly, while keeping call sites terse.
Choose how two values should be compared using a Equated.Comparator:
The detectEquatable comparator attempts to cast values to any Equatable and compare them using ==:
.detectEquatable(
checkBoth: Bool = false,
fallback: Comparator = .dump
)If equatable comparison is not possible, the provided fallback comparator is used.
.const(Bool)– always equal / never equal.custom((Value, Value) -> Bool)– full control.dump– compares the textualdump()output
.defaultEquatable– equivalent to using==directly
The property comparator compares values by a derived equatable projection:
.property(\.someEquatableProperty)
.property { String(reflecting: $0) }.objectID– compare reference identity (only whenValue: AnyObject)
The .localizedDescription comparator is equivalent to .property(\.localizedDescription).
It is typically most useful as a fallback, for example:
.detectEquatable(fallback: .localizedDescription)-
.uncheckedSendable((Value) -> any Equatable)A
property-style comparator for non-sendable projections -
.uncheckedSendable((Value, Value) -> Bool)A
custom-style comparator for non-sendable values
Note
Most users should prefer .detectEquatable() or .property comparators
You can add Equated to an Xcode project by adding it as a package dependency.
- From the File menu, select Swift Packages › Add Package Dependency…
- Enter
"https://github.com/capturecontext/swift-equated"into the package repository URL text field - Choose products you need to link them to your project.
If you use SwiftPM for your project structure, add Equated to your package file.
.package(
url: "git@github.com:capturecontext/swift-equated.git",
.upToNextMinor(from: "0.0.1")
)or via HTTPS
.package(
url: "https://github.com:capturecontext/swift-equated.git",
.upToNextMinor("0.0.1")
)Do not forget about target dependencies:
.product(
name: "Equated",
package: "swift-equated"
)This library is released under the MIT license. See LICENSE for details.