Skip to content

Commit

Permalink
feat: add higher-order functions that operate on key paths
Browse files Browse the repository at this point in the history
  • Loading branch information
EmilioOjeda committed Nov 24, 2022
1 parent 3fe2a16 commit 3adbc50
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 0 deletions.
66 changes: 66 additions & 0 deletions Sources/SwiftExtended/KeyPath.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/// It returns a function that receives a value and allows applying a negation to the value held in the resulting boolean value of the key path.
///
/// Assuming you have a list of posts with either published or unpublished status, you want to filter the unpublished ones. Then, you could do the following:
///
/// ```swift
/// // unpublished posts
/// let unpublished = posts
/// .filter(!\.isPublished)
/// ```
///
/// - Parameters:
/// - keyPath: The key path to the resulting boolean value where the negation is applied.
/// - Returns: A function from a value to a bool.
public prefix func !<Element>(
keyPath: KeyPath<Element, Bool>
) -> (Element) -> Bool {
{ element in
!element[keyPath: keyPath]
}
}

/// It allows for comparing equality among the resulting value of the key path on the left-hand side against the concrete value on the right-hand side.
///
/// Assuming you have to find a post with a given `id`. Then, you could do the following:
///
/// ```swift
/// // find post by id
/// let post = posts
/// .first(where: \.id == id)
/// ```
///
/// - Parameters:
/// - keyPath: A key path from a specific root type to a specific resulting value type.
/// - value: The value to compare against the resulting value of the key path.
/// - Returns: A function returning the resulting boolean of the comparison.
public func == <Element, Value: Equatable>(
keyPath: KeyPath<Element, Value>,
value: Value
) -> (Element) -> Bool {
{ element in
element[keyPath: keyPath] == value
}
}

/// It allows for comparing non-equality among the resulting value of the key path on the left-hand side against the concrete value on the right-hand side.
///
/// Assuming you have to get a list of posts by removing those with the given `id`. Then, you could do the following:
///
/// ```swift
/// // removing post with id
/// let filtered = posts
/// .filter(\.id != id)
/// ```
///
/// - Parameters:
/// - keyPath: A key path from a specific root type to a specific resulting value type.
/// - value: The value to compare against the resulting value of the key path.
/// - Returns: A function returning the resulting boolean of the comparison.
public func != <Element, Value: Equatable>(
keyPath: KeyPath<Element, Value>,
value: Value
) -> (Element) -> Bool {
{ element in
element[keyPath: keyPath] != value
}
}
49 changes: 49 additions & 0 deletions Tests/SwiftExtendedTests/KeyPathTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import XCTest
@testable import SwiftExtended

private protocol Boolean: Hashable {
var boolean: Bool { get }
}

private struct True: Boolean {
let boolean: Bool = true
}

private struct False: Boolean {
let boolean: Bool = false
}

final class KeyPathTests: XCTestCase {
private let booleans: [any Boolean] = [True(), False()]

func testNegationOnKeyPathsAsPredicates() throws {
XCTAssertEqual(
False(),
try XCTUnwrap(
booleans
.filter(!\.boolean)
.first as? False
)
)
}

func testEqualsToAndDifferentThan() throws {
XCTAssertEqual(
True(),
try XCTUnwrap(
booleans
.filter(\.boolean == true)
.first as? True
)
)

XCTAssertEqual(
False(),
try XCTUnwrap(
booleans
.filter(\.boolean != true)
.first as? False
)
)
}
}

0 comments on commit 3adbc50

Please sign in to comment.